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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;

Expand All @@ -30,18 +32,23 @@ public ResponseEntity<List<CommitCraftTemplate>> getAllTemplates() throws IOExce
summary = "Create a dedicated commit template",
description = "Creates a new dedicated commit template if the pattern and model scope are valid.",
responses = {
@ApiResponse(responseCode = "200", description = "Template added successfully"),
@ApiResponse(responseCode = "400", description = "Template already exists")
@ApiResponse(responseCode = "201", description = "Template created successfully"),
@ApiResponse(responseCode = "400", description = "Invalid template format or template already exists")
}
)
@PostMapping("/dedicated")
public ResponseEntity<String> createDedicatedTemplate(@RequestBody CommitCraftTemplate template) throws IOException {
boolean patternAndModelScope = CommitDedicatedTemplateValidator.validatePatternAndModelScope(template);
if (patternAndModelScope) {
commitTemplateService.createDedicatedTemplate(template);
return ResponseEntity.ok("Template added successfully.");
}
return ResponseEntity.badRequest().body("Template already exists.");
public ResponseEntity<Map<String, String>> createDedicatedTemplate(@RequestBody CommitCraftTemplate template) throws IOException {
TemplateOperationResult result = commitTemplateService.createDedicatedTemplate(template);

if (result.success()) {
return ResponseEntity
.status(HttpStatus.CREATED)
.body(Collections.singletonMap("message", result.message()));
}

return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(Collections.singletonMap("error", result.message()));
}

@Operation(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,79 @@
package pl.commit.craft.template;

import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
class CommitDedicatedTemplateValidator {
private static final String PATTERN_VALIDATE_MODEL = "\\{(\\w+)\\}(-\\{(\\w+)\\})*";
private static final String PLACEHOLDER_REGEX = "\\{(\\w+)\\}";
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile(PLACEHOLDER_REGEX);

static boolean validatePatternAndModelScope(CommitCraftTemplate template) {
log.info("Validating pattern and model scope starting");
String pattern = template.getPattern();
Map<String, Object> model = template.getModel();
/**
* For backward compatibility
*/
public static boolean validatePatternAndModelScope(CommitCraftTemplate template) {
return validatePatternAndModelScopeDetailed(template).isValid();
}

Set<String> modelKeys = model.keySet();
/**
* Validates that all keys in the model exist in the pattern and vice versa.
*
* @param template The commit template to validate
* @return result of validation with details about any mismatches
*/
public static ValidationResult validatePatternAndModelScopeDetailed(CommitCraftTemplate template) {
log.info("Validating pattern and model scope starting for template: {}", template.getName());

boolean matches = true;
for (String key : modelKeys) {
if (!pattern.contains(key)) {
log.warn("Pattern is missing key: {}", key);
matches = false;
}
}
Set<String> modelKeys = template.getModel().keySet();
Set<String> patternKeys = extractPlaceholdersFromPattern(template.getPattern());

String[] patternWords = pattern.split(PATTERN_VALIDATE_MODEL);
for (String word : patternWords) {
if (!modelKeys.contains(word)) {
log.warn("Pattern contains an extra key not in the model: {}", word);
matches = false;
}
}
Set<String> missingInPattern = findMissingKeys(modelKeys, patternKeys);
Set<String> extraInPattern = findMissingKeys(patternKeys, modelKeys);

boolean isValid = missingInPattern.isEmpty() && extraInPattern.isEmpty();

if (matches) {
if (isValid) {
log.info("Pattern matches the model keys.");
return ValidationResult.valid();
} else {
log.warn("Pattern does not match the model keys.");
if (!missingInPattern.isEmpty()) {
log.warn("Pattern is missing keys: {}", missingInPattern);
}
if (!extraInPattern.isEmpty()) {
log.warn("Pattern contains extra keys not in the model: {}", extraInPattern);
}
return ValidationResult.invalid(missingInPattern, extraInPattern);
}
}


/**
* Extracts all placeholder keys from the pattern string.
*
* @param pattern The pattern string containing placeholders
* @return A set of placeholder keys
*/
private static Set<String> extractPlaceholdersFromPattern(String pattern) {
Set<String> patternKeys = new HashSet<>();
Matcher matcher = PLACEHOLDER_PATTERN.matcher(pattern);

while (matcher.find()) {
patternKeys.add(matcher.group(1));
}

return patternKeys;
}

return matches;
/**
* Finds keys that are in the first set but not in the second set
*/
private static Set<String> findMissingKeys(Set<String> sourceKeys, Set<String> targetKeys) {
return sourceKeys.stream()
.filter(key -> !targetKeys.contains(key))
.collect(Collectors.toSet());
}
}
57 changes: 53 additions & 4 deletions src/main/java/pl/commit/craft/template/CommitTemplateService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
Expand All @@ -14,6 +15,7 @@
import java.util.List;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
class CommitTemplateService {
Expand Down Expand Up @@ -78,10 +80,57 @@ public List<CommitCraftTemplate> readTemplates() throws IOException {
});
}

public void createDedicatedTemplate(CommitCraftTemplate newTemplate) throws IOException {
List<CommitCraftTemplate> templates = readTemplates();
templates.add(newTemplate);
saveTemplates(templates);
/**
* Creates a dedicated template after validating it.
*
* @param template The template to create
* @return Result containing success/failure status and a message
* @throws IOException If there's an issue accessing the template storage
*/
public TemplateOperationResult createDedicatedTemplate(CommitCraftTemplate template) throws IOException {
ValidationResult validationResult =
CommitDedicatedTemplateValidator.validatePatternAndModelScopeDetailed(template);

if (!validationResult.isValid()) {
String errorMessage = validationResult.getErrorMessage();
log.warn("Template validation failed: {}", errorMessage);
return new TemplateOperationResult(false, errorMessage);
}

if (templateExists(template.getName())) {
log.warn("Template with name '{}' already exists", template.getName());
return new TemplateOperationResult(false, "Template with name '" + template.getName() + "' already exists");
}

saveTemplate(template);
log.info("Template '{}' created successfully", template.getName());
return new TemplateOperationResult(true, "Template created successfully");
}

/**
* Checks if a template with the given name already exists
*
* @param templateName Name of the template to check
* @return true if the template exists, false otherwise
* @throws IOException If there's an issue accessing the template storage
*/
private boolean templateExists(String templateName) throws IOException {
List<CommitCraftTemplate> existingTemplates = getAllTemplates();
return existingTemplates.stream()
.anyMatch(template -> template.getName().equals(templateName));
}

/**
* Saves a new template to the dedicated templates file
*
* @param template The template to save
* @throws IOException If there's an issue accessing or writing to the template storage
*/
private void saveTemplate(CommitCraftTemplate template) throws IOException {
List<CommitCraftTemplate> existingTemplates = readTemplates();
existingTemplates.add(template);
saveTemplates(existingTemplates);
log.debug("Template saved successfully: {}", template.getName());
}

public void removeDedicatedTemplate(String dedicatedTemplateName) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package pl.commit.craft.template;

record TemplateOperationResult(boolean success, String message) {
}
56 changes: 56 additions & 0 deletions src/main/java/pl/commit/craft/template/ValidationResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package pl.commit.craft.template;

import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Set;

@AllArgsConstructor
class ValidationResult {
@Getter
private final boolean valid;
private final Set<String> missingInPattern;
private final Set<String> extraInPattern;

/**
* Creates a successful validation result
*/
public static ValidationResult valid() {
return new ValidationResult(true, Set.of(), Set.of());
}

/**
* Creates a validation result with errors
*/
public static ValidationResult invalid(Set<String> missingInPattern, Set<String> extraInPattern) {
return new ValidationResult(false, missingInPattern, extraInPattern);
}

/**
* Generates a detailed error message for invalid templates
*/
public String getErrorMessage() {
if (valid) {
return "Template is valid";
}

StringBuilder message = new StringBuilder("Invalid template format: ");

if (!missingInPattern.isEmpty()) {
message.append("Keys missing in pattern: [")
.append(String.join(", ", missingInPattern))
.append("]");

if (!extraInPattern.isEmpty()) {
message.append("; ");
}
}

if (!extraInPattern.isEmpty()) {
message.append("Missing keys in model: [")
.append(String.join(", ", extraInPattern))
.append("]");
}

return message.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,10 @@ public ResponseEntity<String> generateCommit(@RequestParam String templateName,
String commitMessage = service.generateCommit(templateName, commitData);
return ResponseEntity.ok(commitMessage);
}

@PostMapping("/generate-dedicated")
public ResponseEntity<String> generateDedicatedCommit(@RequestParam String templateName, @RequestBody JsonNode commitData) throws IOException {
String commitMessage = service.generateDedicatedCommit(templateName, commitData);
return ResponseEntity.ok(commitMessage);
}
}
Loading