]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-21131 Add response to new endpoint apiv2 rules
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>
Fri, 1 Dec 2023 13:21:02 +0000 (14:21 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 8 Dec 2023 20:03:04 +0000 (20:03 +0000)
36 files changed:
server/sonar-webserver-common/src/main/java/org/sonar/server/common/rule/service/RuleInformation.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/rule/service/RuleService.java
server/sonar-webserver-common/src/main/java/org/sonar/server/common/text/Macro.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/text/MacroInterpreter.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/text/RuleMacro.java [new file with mode: 0644]
server/sonar-webserver-common/src/main/java/org/sonar/server/common/text/package-info.java [new file with mode: 0644]
server/sonar-webserver-common/src/test/java/org/sonar/server/common/text/MacroInterpreterTest.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/controller/DefaultRuleController.java
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/converter/RuleRestResponseGenerator.java
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/CleanCodeAttributeCategoryRestEnum.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/CleanCodeAttributeRestEnum.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/ImpactSeverityRestEnum.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/RuleStatusRestEnum.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/RuleTypeRestEnum.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/SoftwareQualityRestEnum.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/package-info.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/response/RuleDescriptionSectionContextRestResponse.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/response/RuleDescriptionSectionRestResponse.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/response/RuleImpactRestResponse.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/response/RuleParameterRestResponse.java [new file with mode: 0644]
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/response/RuleRestResponse.java
server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/rule/controller/DefaultRuleControllerTest.java
server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/rule/converter/RuleRestResponseGeneratorTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/CreateActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/ListActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/ShowActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/UpdateActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/text/Macro.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/text/MacroInterpreter.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/text/RuleMacro.java [deleted file]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/text/package-info.java [deleted file]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/text/MacroInterpreterTest.java [deleted file]
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java

diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/rule/service/RuleInformation.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/rule/service/RuleInformation.java
new file mode 100644 (file)
index 0000000..7f1707c
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.common.rule.service;
+
+import java.util.List;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+
+public record RuleInformation(RuleDto ruleDto, List<RuleParamDto> params) {
+}
index f43f952782be52b02f81e4fa45ac2fd8faf69f66..8ff3568f417562d5c92cdcfbcb6a59104c7b975b 100644 (file)
  */
 package org.sonar.server.common.rule.service;
 
-import org.sonar.db.rule.RuleDto;
-
 public class RuleService {
 
-  RuleDto create(CreateRuleRequest request) {
+  public RuleInformation create(CreateRuleRequest request) {
     return null;
   }
 
diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/text/Macro.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/text/Macro.java
new file mode 100644 (file)
index 0000000..bcef7c8
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.common.text;
+
+public interface Macro {
+
+  String getRegex();
+
+  String getReplacement();
+}
diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/text/MacroInterpreter.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/text/MacroInterpreter.java
new file mode 100644 (file)
index 0000000..512a380
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.common.text;
+
+import java.util.List;
+import org.sonar.api.platform.Server;
+import org.sonar.api.server.ServerSide;
+
+@ServerSide
+public class MacroInterpreter {
+  private final List<Macro> macros;
+
+  public MacroInterpreter(Server server) {
+    this.macros = List.of(
+      new RuleMacro(server.getContextPath())
+    );
+  }
+
+  public String interpret(String text) {
+    String textReplaced = text;
+    for (Macro macro : macros) {
+      textReplaced = textReplaced.replaceAll(macro.getRegex(), macro.getReplacement());
+    }
+    return textReplaced;
+  }
+}
diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/text/RuleMacro.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/text/RuleMacro.java
new file mode 100644 (file)
index 0000000..3b156e6
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.common.text;
+
+class RuleMacro implements Macro {
+
+  private static final String COLON = "%3A";
+  private final String contextPath;
+
+  RuleMacro(String contextPath) {
+    this.contextPath = contextPath;
+  }
+
+  /**
+   * First parameter is the repository, second one is the rule key. Exemple : {rule:java:ArchitecturalConstraint}
+   */
+  @Override
+  public String getRegex() {
+    return "\\{rule:([a-zA-Z0-9._-]++):([a-zA-Z0-9._-]++)\\}";
+  }
+
+  @Override
+  public String getReplacement() {
+    return "<a href='" + contextPath + "/coding_rules#rule_key=$1" + COLON + "$2'>$2</a>";
+  }
+}
diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/text/package-info.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/text/package-info.java
new file mode 100644 (file)
index 0000000..87a2ada
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.common.text;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/text/MacroInterpreterTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/text/MacroInterpreterTest.java
new file mode 100644 (file)
index 0000000..c146a89
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.common.text;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.platform.Server;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MacroInterpreterTest {
+
+  String path = "http://sonar";
+  MacroInterpreter interpreter;
+
+  @Before
+  public void setUp() {
+    Server server = mock(Server.class);
+    when(server.getContextPath()).thenReturn(path);
+    interpreter = new MacroInterpreter(server);
+  }
+
+  @Test
+  public void should_do_nothing_if_no_macro_detected() {
+    String origin = "nothing to do";
+    String result = interpreter.interpret(origin);
+    assertThat(result).isEqualTo(origin);
+  }
+
+  @Test
+  public void should_replace_rule_macro() {
+    // key of repository and rule can contain alphanumeric latin characters, dashes, underscores and dots
+    String ruleKey = "Some_Repo-Key.1:Some_Rule-Key.1";
+    String origin = "See {rule:" + ruleKey + "} for detail.";
+    String result = interpreter.interpret(origin);
+    // colon should be escaped
+    assertThat(result).isEqualTo("See <a href='" + path + "/coding_rules#rule_key=Some_Repo-Key.1%3ASome_Rule-Key.1'>Some_Rule-Key.1</a> for detail.");
+  }
+
+}
index a7a0f87d1d41f5c5c858b0284fcf1ef622bb6683..dae75304e64c904c3decfafe670e758c99db86f6 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.v2.api.rule.controller;
 
+import org.sonar.server.common.rule.service.RuleInformation;
 import org.sonar.server.common.rule.service.RuleService;
 import org.sonar.server.user.UserSession;
 import org.sonar.server.v2.api.rule.converter.RuleRestResponseGenerator;
@@ -39,8 +40,9 @@ public class DefaultRuleController implements RuleController {
 
   @Override
   public RuleRestResponse create(RuleCreateRestRequest request) {
-    return null;
-  }
 
 
+    RuleInformation ruleInformation = ruleService.create(null);
+    return ruleRestResponseGenerator.toRuleRestResponse(ruleInformation);
+  }
 }
index 3392b26f0fb2a11fcb961befb7a84caa799e4461..f0dd41434c448653486795505dc1fcbbcdc7d45d 100644 (file)
  */
 package org.sonar.server.v2.api.rule.converter;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import org.jetbrains.annotations.Nullable;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.rules.CleanCodeAttribute;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.db.issue.ImpactDto;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.markdown.Markdown;
+import org.sonar.server.common.rule.service.RuleInformation;
+import org.sonar.server.common.text.MacroInterpreter;
+import org.sonar.server.rule.RuleDescriptionFormatter;
+import org.sonar.server.v2.api.rule.enums.CleanCodeAttributeCategoryRestEnum;
+import org.sonar.server.v2.api.rule.enums.CleanCodeAttributeRestEnum;
+import org.sonar.server.v2.api.rule.enums.ImpactSeverityRestEnum;
+import org.sonar.server.v2.api.rule.enums.RuleStatusRestEnum;
+import org.sonar.server.v2.api.rule.enums.RuleTypeRestEnum;
+import org.sonar.server.v2.api.rule.enums.SoftwareQualityRestEnum;
+import org.sonar.server.v2.api.rule.response.RuleDescriptionSectionContextRestResponse;
+import org.sonar.server.v2.api.rule.response.RuleDescriptionSectionRestResponse;
+import org.sonar.server.v2.api.rule.response.RuleImpactRestResponse;
+import org.sonar.server.v2.api.rule.response.RuleParameterRestResponse;
+import org.sonar.server.v2.api.rule.response.RuleRestResponse;
+
+import static java.util.Optional.ofNullable;
+import static org.sonar.db.rule.RuleDto.Format.MARKDOWN;
+
 public class RuleRestResponseGenerator {
 
+  private final Languages languages;
+  private final MacroInterpreter macroInterpreter;
+  private final RuleDescriptionFormatter ruleDescriptionFormatter;
+
+  public RuleRestResponseGenerator(Languages languages, MacroInterpreter macroInterpreter, RuleDescriptionFormatter ruleDescriptionFormatter) {
+
+    this.languages = languages;
+    this.macroInterpreter = macroInterpreter;
+    this.ruleDescriptionFormatter = ruleDescriptionFormatter;
+  }
+
+  public RuleRestResponse toRuleRestResponse(RuleInformation ruleInformation) {
+
+    RuleRestResponse.Builder builder = RuleRestResponse.Builder.builder();
+    RuleDto ruleDto = ruleInformation.ruleDto();
+    builder
+      .setId(ruleDto.getUuid())
+      .setKey(ruleDto.getKey().toString())
+      .setRepositoryKey(ruleDto.getRepositoryKey())
+      .setName(ruleDto.getName())
+      .setSeverity(ruleDto.getSeverityString())
+      .setType(RuleTypeRestEnum.from(RuleType.valueOf(ruleDto.getType())))
+      .setImpacts(toImpactRestResponse(ruleDto.getDefaultImpacts()))
+      .setCleanCodeAttribute(CleanCodeAttributeRestEnum.from(ruleDto.getCleanCodeAttribute()))
+      .setCleanCodeAttributeCategory(ofNullable(ruleDto.getCleanCodeAttribute())
+        .map(CleanCodeAttribute::getAttributeCategory)
+        .map(CleanCodeAttributeCategoryRestEnum::from)
+        .orElse(null))
+      .setStatus(RuleStatusRestEnum.from(ruleDto.getStatus()))
+      .setExternal(ruleDto.isExternal())
+      .setCreatedAt(toDateTime(ruleDto.getCreatedAt()))
+      .setGapDescription(ruleDto.getGapDescription())
+      .setHtmlNote(ofNullable(ruleDto.getNoteData()).map(n -> macroInterpreter.interpret(Markdown.convertToHtml(n))).orElse(null))
+      .setMarkdownNote(ruleDto.getNoteData())
+      .setEducationPrinciples(new ArrayList<>(ruleDto.getEducationPrinciples()))
+      .setTemplate(ruleDto.isTemplate())
+      .setTemplateId(ruleDto.getTemplateUuid())
+      .setTags(new ArrayList<>(ruleDto.getTags()))
+      .setSystemTags(new ArrayList<>(ruleDto.getSystemTags()))
+      .setLanguageKey(ruleDto.getLanguage())
+      .setLanguageName(getLanguageName(ruleDto.getLanguage()))
+      .setParameters(toRuleParameterResponse(ruleInformation.params()));
+
+    setDescriptionFields(builder, ruleDto);
+    setRemediationFunctionFields(builder, ruleDto);
+
+    if (ruleDto.isAdHoc()) {
+      ofNullable(ruleDto.getAdHocName()).ifPresent(builder::setName);
+      ofNullable(ruleDto.getAdHocDescription())
+        .map(this::toDescriptionSectionResponse)
+        .ifPresent(section -> builder.setDescriptionSections(List.of(section)));
+      ofNullable(ruleDto.getAdHocSeverity()).ifPresent(builder::setSeverity);
+      ofNullable(ruleDto.getAdHocType()).ifPresent(type -> builder.setType(RuleTypeRestEnum.from(RuleType.valueOf(type))));
+    }
+    return builder.build();
+  }
+
+  private static void setRemediationFunctionFields(RuleRestResponse.Builder builder, RuleDto ruleDto) {
+    ofNullable(debtRemediationFunction(ruleDto))
+      .ifPresent(function -> {
+        builder.setRemediationFunctionBaseEffort(function.baseEffort());
+        builder.setRemediationFunctionGapMultiplier(function.gapMultiplier());
+        ofNullable(function.type()).map(Enum::name).ifPresent(builder::setRemediationFunctionType);
+      });
+  }
+
+  private static List<RuleParameterRestResponse> toRuleParameterResponse(List<RuleParamDto> ruleParamDtos) {
+    return ruleParamDtos.stream()
+      .map(p -> new RuleParameterRestResponse(p.getName(), Markdown.convertToHtml(p.getDescription()), p.getDefaultValue(), p.getType()))
+      .toList();
+  }
+
+  @CheckForNull
+  private String getLanguageName(@Nullable String languageKey) {
+    if (languageKey == null) {
+      return null;
+    }
+    Language language = languages.get(languageKey);
+    return language == null ? languageKey : language.getName();
+  }
+
+  private void setDescriptionFields(RuleRestResponse.Builder builder, RuleDto ruleDto) {
+    builder.setDescriptionSections(ruleDto.getRuleDescriptionSectionDtos().stream()
+      .map(sectionDto -> toDescriptionSectionResponse(ruleDto, sectionDto))
+      .toList());
+
+    String htmlDescription = ruleDescriptionFormatter.getDescriptionAsHtml(ruleDto);
+    if (MARKDOWN.equals(ruleDto.getDescriptionFormat())) {
+      Optional.ofNullable(ruleDto.getDefaultRuleDescriptionSection())
+        .map(RuleDescriptionSectionDto::getContent)
+        .ifPresent(builder::setMarkdownDescription);
+    } else if (htmlDescription != null) {
+      builder.setMarkdownDescription(macroInterpreter.interpret(htmlDescription));
+    }
+  }
+
+  private RuleDescriptionSectionRestResponse toDescriptionSectionResponse(RuleDto ruleDto, RuleDescriptionSectionDto section) {
+    String htmlContent = ruleDescriptionFormatter.toHtml(ruleDto.getDescriptionFormat(), section);
+    String interpretedHtmlContent = macroInterpreter.interpret(htmlContent);
+    return new RuleDescriptionSectionRestResponse(section.getKey(), interpretedHtmlContent,
+      ofNullable(section.getContext())
+        .map(c -> new RuleDescriptionSectionContextRestResponse(c.getKey(), c.getDisplayName()))
+        .orElse(null));
+  }
+
+  private RuleDescriptionSectionRestResponse toDescriptionSectionResponse(String description) {
+    return new RuleDescriptionSectionRestResponse(RuleDescriptionSectionDto.DEFAULT_KEY, macroInterpreter.interpret(description), null);
+  }
+
+  private static List<RuleImpactRestResponse> toImpactRestResponse(Set<ImpactDto> defaultImpacts) {
+    return defaultImpacts.stream()
+      .map(i -> new RuleImpactRestResponse(SoftwareQualityRestEnum.from(i.getSoftwareQuality()), ImpactSeverityRestEnum.from(i.getSeverity())))
+      .toList();
+  }
+
+  @CheckForNull
+  private static DebtRemediationFunction defaultDebtRemediationFunction(final RuleDto ruleDto) {
+    final String function = ruleDto.getDefRemediationFunction();
+    if (function == null || function.isEmpty()) {
+      return null;
+    } else {
+      return new DefaultDebtRemediationFunction(
+        DebtRemediationFunction.Type.valueOf(function.toUpperCase(Locale.ENGLISH)),
+        ruleDto.getDefRemediationGapMultiplier(),
+        ruleDto.getDefRemediationBaseEffort());
+    }
+  }
+
+  @CheckForNull
+  private static DebtRemediationFunction debtRemediationFunction(RuleDto ruleDto) {
+    final String function = ruleDto.getRemediationFunction();
+    if (function == null || function.isEmpty()) {
+      return defaultDebtRemediationFunction(ruleDto);
+    } else {
+      return new DefaultDebtRemediationFunction(
+        DebtRemediationFunction.Type.valueOf(function.toUpperCase(Locale.ENGLISH)),
+        ruleDto.getRemediationGapMultiplier(),
+        ruleDto.getRemediationBaseEffort());
+    }
+  }
+
+  private static String toDateTime(@Nullable Long dateTimeMs) {
+    return Optional.ofNullable(dateTimeMs).map(DateUtils::formatDateTime).orElse(null);
+  }
+
 }
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/CleanCodeAttributeCategoryRestEnum.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/CleanCodeAttributeCategoryRestEnum.java
new file mode 100644 (file)
index 0000000..bd07a53
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.rule.enums;
+
+import java.util.Arrays;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.rules.CleanCodeAttributeCategory;
+
+public enum CleanCodeAttributeCategoryRestEnum {
+  ADAPTABLE(CleanCodeAttributeCategory.ADAPTABLE),
+  CONSISTENT(CleanCodeAttributeCategory.CONSISTENT),
+  INTENTIONAL(CleanCodeAttributeCategory.INTENTIONAL),
+  RESPONSIBLE(CleanCodeAttributeCategory.RESPONSIBLE);
+
+  private final CleanCodeAttributeCategory cleanCodeAttributeCategory;
+
+  CleanCodeAttributeCategoryRestEnum(CleanCodeAttributeCategory cleanCodeAttributeCategory) {
+    this.cleanCodeAttributeCategory = cleanCodeAttributeCategory;
+  }
+
+  @CheckForNull
+  public static CleanCodeAttributeCategoryRestEnum from(@Nullable CleanCodeAttributeCategory cleanCodeAttributeCategory) {
+    if (cleanCodeAttributeCategory == null) {
+      return null;
+    }
+    return Arrays.stream(CleanCodeAttributeCategoryRestEnum.values())
+      .filter(cleanCodeAttributeCategoryRestEnum -> cleanCodeAttributeCategoryRestEnum.cleanCodeAttributeCategory.equals(cleanCodeAttributeCategory))
+      .findFirst()
+      .orElseThrow(() -> new IllegalArgumentException("Unsupported clean code attribute category: " + cleanCodeAttributeCategory));
+  }
+
+
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/CleanCodeAttributeRestEnum.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/CleanCodeAttributeRestEnum.java
new file mode 100644 (file)
index 0000000..6bfa406
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.rule.enums;
+
+import java.util.Arrays;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.rules.CleanCodeAttribute;
+
+public enum CleanCodeAttributeRestEnum {
+  CONVENTIONAL(CleanCodeAttribute.CONVENTIONAL),
+  FORMATTED(CleanCodeAttribute.FORMATTED),
+  IDENTIFIABLE(CleanCodeAttribute.IDENTIFIABLE),
+
+  CLEAR(CleanCodeAttribute.CLEAR),
+  COMPLETE(CleanCodeAttribute.COMPLETE),
+  EFFICIENT(CleanCodeAttribute.EFFICIENT),
+  LOGICAL(CleanCodeAttribute.LOGICAL),
+
+  DISTINCT(CleanCodeAttribute.DISTINCT),
+  FOCUSED(CleanCodeAttribute.FOCUSED),
+  MODULAR(CleanCodeAttribute.MODULAR),
+  TESTED(CleanCodeAttribute.TESTED),
+
+  LAWFUL(CleanCodeAttribute.LAWFUL),
+  RESPECTFUL(CleanCodeAttribute.RESPECTFUL),
+  TRUSTWORTHY(CleanCodeAttribute.TRUSTWORTHY);
+
+  private final CleanCodeAttribute cleanCodeAttribute;
+
+  CleanCodeAttributeRestEnum(CleanCodeAttribute cleanCodeAttribute) {
+    this.cleanCodeAttribute = cleanCodeAttribute;
+  }
+
+  @CheckForNull
+  public static CleanCodeAttributeRestEnum from(@Nullable CleanCodeAttribute cleanCodeAttribute) {
+    if (cleanCodeAttribute == null) {
+      return null;
+    }
+    return Arrays.stream(CleanCodeAttributeRestEnum.values())
+      .filter(cleanCodeAttributeRestEnum -> cleanCodeAttributeRestEnum.cleanCodeAttribute.equals(cleanCodeAttribute))
+      .findFirst()
+      .orElseThrow(() -> new IllegalArgumentException("Unsupported clean code attribute: " + cleanCodeAttribute));
+  }
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/ImpactSeverityRestEnum.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/ImpactSeverityRestEnum.java
new file mode 100644 (file)
index 0000000..469797c
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.rule.enums;
+
+import java.util.Arrays;
+import org.sonar.api.issue.impact.Severity;
+
+public enum ImpactSeverityRestEnum {
+  LOW(Severity.LOW),
+  MEDIUM(Severity.MEDIUM),
+  HIGH(Severity.HIGH);
+
+  private final Severity severity;
+
+  ImpactSeverityRestEnum(Severity severity) {
+
+    this.severity = severity;
+  }
+
+  public static ImpactSeverityRestEnum from(Severity severity) {
+    return Arrays.stream(ImpactSeverityRestEnum.values())
+      .filter(severityRestResponse -> severityRestResponse.severity.equals(severity))
+      .findFirst()
+      .orElseThrow(() -> new IllegalArgumentException("Unsupported impact severity: " + severity));
+  }
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/RuleStatusRestEnum.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/RuleStatusRestEnum.java
new file mode 100644 (file)
index 0000000..071b6ad
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.rule.enums;
+
+import java.util.Arrays;
+import org.sonar.api.rule.RuleStatus;
+
+public enum RuleStatusRestEnum {
+  BETA(RuleStatus.BETA),
+  DEPRECATED(RuleStatus.DEPRECATED),
+  READY(RuleStatus.READY),
+  REMOVED(RuleStatus.REMOVED);
+
+  private final RuleStatus ruleStatus;
+
+  RuleStatusRestEnum(RuleStatus ruleStatus) {
+    this.ruleStatus = ruleStatus;
+  }
+
+  public static RuleStatusRestEnum from(RuleStatus ruleStatus) {
+    return Arrays.stream(RuleStatusRestEnum.values())
+      .filter(ruleStatusRestEnum -> ruleStatusRestEnum.ruleStatus.equals(ruleStatus))
+      .findFirst()
+      .orElseThrow(() -> new IllegalArgumentException("Unsupported RuleStatus: " + ruleStatus));
+  }
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/RuleTypeRestEnum.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/RuleTypeRestEnum.java
new file mode 100644 (file)
index 0000000..09b2179
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.rule.enums;
+
+import java.util.Arrays;
+import org.sonar.api.rules.RuleType;
+
+public enum RuleTypeRestEnum {
+  CODE_SMELL(RuleType.CODE_SMELL),
+  BUG(RuleType.BUG),
+  VULNERABILITY(RuleType.VULNERABILITY),
+  SECURITY_HOTSPOT(RuleType.SECURITY_HOTSPOT),
+  ;
+
+  private final RuleType ruleType;
+
+  RuleTypeRestEnum(RuleType ruleType) {
+    this.ruleType = ruleType;
+  }
+
+  public static RuleTypeRestEnum from(RuleType ruleType) {
+    return Arrays.stream(RuleTypeRestEnum.values())
+      .filter(ruleTypeRestEnum -> ruleTypeRestEnum.ruleType.equals(ruleType))
+      .findFirst()
+      .orElseThrow(() -> new IllegalArgumentException("Unsupported RuleType: " + ruleType));
+  }
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/SoftwareQualityRestEnum.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/SoftwareQualityRestEnum.java
new file mode 100644 (file)
index 0000000..95ab514
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.rule.enums;
+
+import java.util.Arrays;
+import org.sonar.api.issue.impact.SoftwareQuality;
+
+public enum SoftwareQualityRestEnum {
+  MAINTAINABILITY(SoftwareQuality.MAINTAINABILITY),
+  RELIABILITY(SoftwareQuality.RELIABILITY),
+  SECURITY(SoftwareQuality.SECURITY);
+
+
+  private final SoftwareQuality softwareQuality;
+
+  SoftwareQualityRestEnum(SoftwareQuality softwareQuality) {
+
+    this.softwareQuality = softwareQuality;
+  }
+
+  public static SoftwareQualityRestEnum from(SoftwareQuality softwareQuality) {
+    return Arrays.stream(SoftwareQualityRestEnum.values())
+      .filter(softwareQualityRest -> softwareQualityRest.softwareQuality.equals(softwareQuality))
+      .findFirst()
+      .orElseThrow(() -> new IllegalArgumentException("Unsupported SoftwareQuality: " + softwareQuality));
+  }
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/package-info.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/enums/package-info.java
new file mode 100644 (file)
index 0000000..394f8f8
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.v2.api.rule.enums;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/response/RuleDescriptionSectionContextRestResponse.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/response/RuleDescriptionSectionContextRestResponse.java
new file mode 100644 (file)
index 0000000..03cf635
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.rule.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(accessMode = Schema.AccessMode.READ_ONLY)
+public record RuleDescriptionSectionContextRestResponse(
+  String key,
+  String displayName
+
+) {
+
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/response/RuleDescriptionSectionRestResponse.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/response/RuleDescriptionSectionRestResponse.java
new file mode 100644 (file)
index 0000000..f24646b
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.rule.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import javax.annotation.Nullable;
+
+public record RuleDescriptionSectionRestResponse(
+  @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+  String key,
+  @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+  String content,
+  @Nullable
+  @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+  RuleDescriptionSectionContextRestResponse context
+
+) {
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/response/RuleImpactRestResponse.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/response/RuleImpactRestResponse.java
new file mode 100644 (file)
index 0000000..a0efcf7
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.rule.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.sonar.server.v2.api.rule.enums.ImpactSeverityRestEnum;
+import org.sonar.server.v2.api.rule.enums.SoftwareQualityRestEnum;
+
+@Schema(accessMode = Schema.AccessMode.READ_ONLY)
+public record RuleImpactRestResponse(
+  SoftwareQualityRestEnum softwareQuality,
+  ImpactSeverityRestEnum severity
+) {
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/response/RuleParameterRestResponse.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/rule/response/RuleParameterRestResponse.java
new file mode 100644 (file)
index 0000000..418a67f
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.rule.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import javax.annotation.Nullable;
+
+public record RuleParameterRestResponse(
+
+  String key,
+  @Schema(accessMode = Schema.AccessMode.READ_ONLY)
+  String htmlDescription,
+  @Nullable
+  String defaultValue,
+  @Schema(allowableValues = {
+    "STRING",
+    "TEXT",
+    "BOOLEAN",
+    "INTEGER",
+    "FLOAT"
+  }, accessMode = Schema.AccessMode.READ_ONLY)
+  String type
+) {
+}
index 3b1d9f744bc3b8024373cfecc808ae9f1edee982..d9da2d2d3d7d21aa52af5039d0776fb06f5e2bec 100644 (file)
  */
 package org.sonar.server.v2.api.rule.response;
 
-public record RuleRestResponse() {
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.sonar.server.v2.api.rule.enums.CleanCodeAttributeCategoryRestEnum;
+import org.sonar.server.v2.api.rule.enums.CleanCodeAttributeRestEnum;
+import org.sonar.server.v2.api.rule.enums.RuleStatusRestEnum;
+import org.sonar.server.v2.api.rule.enums.RuleTypeRestEnum;
+
+@Schema(accessMode = Schema.AccessMode.READ_ONLY)
+public record RuleRestResponse(
+  String id,
+  String key,
+  String repositoryKey,
+  String name,
+  @Nullable
+  String severity,
+  RuleTypeRestEnum type,
+  List<RuleImpactRestResponse> impacts,
+  @Nullable
+  CleanCodeAttributeRestEnum cleanCodeAttribute,
+  @Nullable
+  CleanCodeAttributeCategoryRestEnum cleanCodeAttributeCategory,
+  @Nullable
+  RuleStatusRestEnum status,
+  boolean external,
+  @Nullable
+  String createdAt,
+  List<RuleDescriptionSectionRestResponse> descriptionSections,
+  @Schema(accessMode = Schema.AccessMode.READ_WRITE)
+  String markdownDescription,
+  @Nullable
+  String gapDescription,
+  @Nullable
+  String htmlNote,
+  @Nullable
+  String markdownNote,
+  List<String> educationPrinciples,
+  boolean template,
+  @Nullable
+  String templateId,
+  @Schema(accessMode = Schema.AccessMode.READ_WRITE)
+  List<String> tags,
+  List<String> systemTags,
+  @Nullable
+  String languageKey,
+  @Nullable
+  String languageName,
+  List<RuleParameterRestResponse> parameters,
+  String remediationFunctionType,
+  String remediationFunctionGapMultiplier,
+  String remediationFunctionBaseEffort
+) {
+
+
+  public static final class Builder {
+    private String id;
+    private String key;
+    private String repositoryKey;
+    private String name;
+    private String severity;
+    private RuleTypeRestEnum type;
+    private List<RuleImpactRestResponse> impacts;
+    private CleanCodeAttributeRestEnum cleanCodeAttribute;
+    private CleanCodeAttributeCategoryRestEnum cleanCodeAttributeCategory;
+    private RuleStatusRestEnum status;
+    private boolean external;
+    private String createdAt;
+    private List<RuleDescriptionSectionRestResponse> descriptionSections;
+    private String markdownDescription;
+    private String gapDescription;
+    private String htmlNote;
+    private String markdownNote;
+    private List<String> educationPrinciples;
+    private boolean template;
+    private String templateId;
+    private List<String> tags;
+    private List<String> systemTags;
+    private String languageKey;
+    private String languageName;
+    private List<RuleParameterRestResponse> parameters;
+    private String remediationFunctionType;
+    private String remediationFunctionGapMultiplier;
+    private String remediationFunctionBaseEffort;
+
+    private Builder() {
+    }
+
+    public static Builder builder() {
+      return new Builder();
+    }
+
+    public Builder setId(String id) {
+      this.id = id;
+      return this;
+    }
+
+    public Builder setKey(String key) {
+      this.key = key;
+      return this;
+    }
+
+    public Builder setRepositoryKey(String repositoryKey) {
+      this.repositoryKey = repositoryKey;
+      return this;
+    }
+
+    public Builder setName(String name) {
+      this.name = name;
+      return this;
+    }
+
+    public Builder setSeverity(@Nullable String severity) {
+      this.severity = severity;
+      return this;
+    }
+
+    public Builder setType(RuleTypeRestEnum type) {
+      this.type = type;
+      return this;
+    }
+
+    public Builder setImpacts(List<RuleImpactRestResponse> impacts) {
+      this.impacts = impacts;
+      return this;
+    }
+
+    public Builder setCleanCodeAttribute(@Nullable CleanCodeAttributeRestEnum cleanCodeAttribute) {
+      this.cleanCodeAttribute = cleanCodeAttribute;
+      return this;
+    }
+
+    public Builder setCleanCodeAttributeCategory(@Nullable CleanCodeAttributeCategoryRestEnum cleanCodeAttributeCategory) {
+      this.cleanCodeAttributeCategory = cleanCodeAttributeCategory;
+      return this;
+    }
+
+    public Builder setStatus(RuleStatusRestEnum status) {
+      this.status = status;
+      return this;
+    }
+
+    public Builder setExternal(boolean external) {
+      this.external = external;
+      return this;
+    }
+
+    public Builder setCreatedAt(@Nullable String createdAt) {
+      this.createdAt = createdAt;
+      return this;
+    }
+
+    public Builder setDescriptionSections(List<RuleDescriptionSectionRestResponse> descriptionSections) {
+      this.descriptionSections = descriptionSections;
+      return this;
+    }
+
+    public Builder setMarkdownDescription(String markdownDescription) {
+      this.markdownDescription = markdownDescription;
+      return this;
+    }
+
+    public Builder setGapDescription(@Nullable String gapDescription) {
+      this.gapDescription = gapDescription;
+      return this;
+    }
+
+    public Builder setHtmlNote(@Nullable String htmlNote) {
+      this.htmlNote = htmlNote;
+      return this;
+    }
+
+    public Builder setMarkdownNote(@Nullable String markdownNote) {
+      this.markdownNote = markdownNote;
+      return this;
+    }
+
+    public Builder setEducationPrinciples(List<String> educationPrinciples) {
+      this.educationPrinciples = educationPrinciples;
+      return this;
+    }
+
+    public Builder setTemplate(boolean template) {
+      this.template = template;
+      return this;
+    }
+
+    public Builder setTemplateId(@Nullable String templateId) {
+      this.templateId = templateId;
+      return this;
+    }
+
+    public Builder setTags(List<String> tags) {
+      this.tags = tags;
+      return this;
+    }
+
+    public Builder setSystemTags(List<String> systemTags) {
+      this.systemTags = systemTags;
+      return this;
+    }
+
+    public Builder setLanguageKey(@Nullable String languageKey) {
+      this.languageKey = languageKey;
+      return this;
+    }
+
+    public Builder setLanguageName(@Nullable String languageName) {
+      this.languageName = languageName;
+      return this;
+    }
+
+    public Builder setParameters(List<RuleParameterRestResponse> parameters) {
+      this.parameters = parameters;
+      return this;
+    }
+
+    public Builder setRemediationFunctionType(@Nullable String remediationFunctionType) {
+      this.remediationFunctionType = remediationFunctionType;
+      return this;
+    }
+
+    public Builder setRemediationFunctionGapMultiplier(@Nullable String remediationFunctionGapMultiplier) {
+      this.remediationFunctionGapMultiplier = remediationFunctionGapMultiplier;
+      return this;
+    }
+
+    public Builder setRemediationFunctionBaseEffort(@Nullable String remediationFunctionBaseEffort) {
+      this.remediationFunctionBaseEffort = remediationFunctionBaseEffort;
+      return this;
+    }
+
+    public RuleRestResponse build() {
+      return new RuleRestResponse(id, key, repositoryKey, name, severity, type, impacts, cleanCodeAttribute, cleanCodeAttributeCategory,
+        status, external, createdAt, descriptionSections, markdownDescription, gapDescription, htmlNote, markdownNote,
+        educationPrinciples, template, templateId, tags, systemTags, languageKey, languageName, parameters, remediationFunctionType,
+        remediationFunctionGapMultiplier, remediationFunctionBaseEffort);
+    }
+  }
 }
index f00c78388aeed167885ec1b1a7261eb1edeee11a..0cb87b69044c623737c06f9b9312e5c72aac4bbd 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.v2.config;
 
 import javax.annotation.Nullable;
+import org.sonar.api.resources.Languages;
 import org.sonar.db.DbClient;
 import org.sonar.server.common.group.service.GroupMembershipService;
 import org.sonar.server.common.group.service.GroupService;
@@ -31,9 +32,11 @@ import org.sonar.server.common.management.ManagedInstanceChecker;
 import org.sonar.server.common.platform.LivenessChecker;
 import org.sonar.server.common.platform.LivenessCheckerImpl;
 import org.sonar.server.common.rule.service.RuleService;
+import org.sonar.server.common.text.MacroInterpreter;
 import org.sonar.server.common.user.service.UserService;
 import org.sonar.server.health.HealthChecker;
 import org.sonar.server.platform.NodeInformation;
+import org.sonar.server.rule.RuleDescriptionFormatter;
 import org.sonar.server.user.SystemPasscode;
 import org.sonar.server.user.UserSession;
 import org.sonar.server.v2.api.group.controller.DefaultGroupController;
@@ -100,8 +103,8 @@ public class PlatformLevel4WebConfig {
   }
 
   @Bean
-  public RuleRestResponseGenerator ruleRestResponseGenerator() {
-    return new RuleRestResponseGenerator();
+  public RuleRestResponseGenerator ruleRestResponseGenerator(Languages languages, MacroInterpreter macroInterpreter, RuleDescriptionFormatter ruleDescriptionFormatter) {
+    return new RuleRestResponseGenerator(languages, macroInterpreter, ruleDescriptionFormatter);
   }
 
   @Bean
index 652a540fa22c40190ca8369c5377d54231268a60..83b40df12fa9ca9cdd97cee30d68bdfc242b10d3 100644 (file)
@@ -27,12 +27,16 @@ import org.sonar.server.common.rule.service.RuleService;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.v2.api.ControllerTester;
 import org.sonar.server.v2.api.rule.converter.RuleRestResponseGenerator;
+import org.sonar.server.v2.api.rule.response.RuleRestResponse;
 import org.springframework.http.MediaType;
 import org.springframework.test.web.servlet.MockMvc;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 import static org.sonar.server.v2.WebApiEndpoints.RULES_ENDPOINT;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -43,8 +47,9 @@ public class DefaultRuleControllerTest {
 
   private final RuleService ruleService = mock();
 
+  private final RuleRestResponseGenerator ruleRestResponseGenerator = mock();
   private final MockMvc mockMvc = ControllerTester
-    .getMockMvc(new DefaultRuleController(userSession, ruleService, new RuleRestResponseGenerator()));
+    .getMockMvc(new DefaultRuleController(userSession, ruleService, ruleRestResponseGenerator));
 
 
   @Test
@@ -53,4 +58,14 @@ public class DefaultRuleControllerTest {
       .andExpectAll(
         status().isOk());
   }
+
+  @Test
+  public void create_shouldReturnExpectedBody() throws Exception {
+    when(ruleRestResponseGenerator.toRuleRestResponse(any())).thenReturn(RuleRestResponse.Builder.builder().setId("id").build());
+
+    mockMvc.perform(post(RULES_ENDPOINT).contentType(MediaType.APPLICATION_JSON_VALUE).content("{}"))
+      .andExpectAll(
+        status().isOk(),
+        content().json("{id: 'id'}"));
+  }
 }
diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/rule/converter/RuleRestResponseGeneratorTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/rule/converter/RuleRestResponseGeneratorTest.java
new file mode 100644 (file)
index 0000000..1e5d10d
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.v2.api.rule.converter;
+
+import java.util.List;
+import java.util.Locale;
+import org.assertj.core.groups.Tuple;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.server.debt.DebtRemediationFunction;
+import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.db.rule.RuleTesting;
+import org.sonar.server.common.rule.service.RuleInformation;
+import org.sonar.server.common.text.MacroInterpreter;
+import org.sonar.server.language.LanguageTesting;
+import org.sonar.server.rule.RuleDescriptionFormatter;
+import org.sonar.server.v2.api.rule.response.RuleRestResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RuleRestResponseGeneratorTest {
+  @Mock
+  private Languages languages;
+
+  @Mock
+  private MacroInterpreter macroInterpreter;
+
+  @Mock
+  RuleDescriptionFormatter ruleDescriptionFormatter;
+
+  @InjectMocks
+  private RuleRestResponseGenerator ruleRestResponseGenerator;
+
+
+  @Test
+  public void toRuleRestResponse_shouldReturnSameFieldForStandardMapping() {
+    when(macroInterpreter.interpret(Mockito.anyString())).thenAnswer(invocation -> "interpreted" + invocation.getArgument(0));
+    when(ruleDescriptionFormatter.toHtml(any(), any())).thenAnswer(invocation -> "html" + ((RuleDescriptionSectionDto) invocation.getArgument(1)).getContent());
+
+    RuleDto dto = RuleTesting.newRule();
+    when(languages.get(dto.getLanguage())).thenReturn(LanguageTesting.newLanguage(dto.getLanguage(), "languageName"));
+
+    RuleRestResponse ruleRestResponse = ruleRestResponseGenerator.toRuleRestResponse(new RuleInformation(dto, List.of()));
+    assertThat(ruleRestResponse.id()).isEqualTo(dto.getUuid());
+    assertThat(ruleRestResponse.key()).isEqualTo(dto.getKey().toString());
+    assertThat(ruleRestResponse.repositoryKey()).isEqualTo(dto.getRepositoryKey());
+    assertThat(ruleRestResponse.name()).isEqualTo(dto.getName());
+    assertThat(ruleRestResponse.descriptionSections()).extracting(s -> s.key(), s -> s.content(), s -> s.context())
+      .containsExactly(dto.getRuleDescriptionSectionDtos().stream().map(s -> tuple(s.getKey(), "interpreted" + "html" + s.getContent(), s.getContext())).toArray(Tuple[]::new));
+    assertThat(ruleRestResponse.severity()).isEqualTo(dto.getSeverityString());
+    assertThat(ruleRestResponse.type().name()).isEqualTo(RuleType.valueOf(dto.getType()).name());
+    assertThat(ruleRestResponse.impacts()).extracting(r -> r.severity().name(), r -> r.softwareQuality().name())
+      .containsExactly(dto.getDefaultImpacts().stream().map(e -> tuple(e.getSeverity().name(), e.getSoftwareQuality().name())).toArray(Tuple[]::new));
+    assertThat(ruleRestResponse.cleanCodeAttribute().name()).isEqualTo(dto.getCleanCodeAttribute().name());
+    assertThat(ruleRestResponse.cleanCodeAttributeCategory().name()).isEqualTo(dto.getCleanCodeAttribute().getAttributeCategory().name());
+    assertThat(ruleRestResponse.status().name()).isEqualTo(dto.getStatus().name());
+    assertThat(ruleRestResponse.external()).isEqualTo(dto.isExternal());
+    assertThat(ruleRestResponse.createdAt()).isEqualTo(DateUtils.formatDateTime(dto.getCreatedAt()));
+    assertThat(ruleRestResponse.gapDescription()).isEqualTo(dto.getGapDescription());
+    assertThat(ruleRestResponse.markdownNote()).isEqualTo(dto.getNoteData());
+    assertThat(ruleRestResponse.educationPrinciples()).containsExactlyElementsOf(dto.getEducationPrinciples());
+    assertThat(ruleRestResponse.template()).isEqualTo(dto.isTemplate());
+    assertThat(ruleRestResponse.templateId()).isEqualTo(dto.getTemplateUuid());
+    assertThat(ruleRestResponse.tags()).containsExactlyElementsOf(dto.getTags());
+    assertThat(ruleRestResponse.systemTags()).containsExactlyElementsOf(dto.getSystemTags());
+    assertThat(ruleRestResponse.languageKey()).isEqualTo(dto.getLanguage());
+    assertThat(ruleRestResponse.languageName()).isEqualTo("languageName");
+
+    DefaultDebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.valueOf(dto.getRemediationFunction().toUpperCase(Locale.ENGLISH)),
+      dto.getRemediationGapMultiplier(),
+      dto.getRemediationBaseEffort());
+    assertThat(ruleRestResponse.remediationFunctionBaseEffort()).isEqualTo(function.baseEffort());
+    assertThat(ruleRestResponse.remediationFunctionGapMultiplier()).isEqualTo(function.gapMultiplier());
+    assertThat(ruleRestResponse.remediationFunctionType()).isEqualTo(dto.getRemediationFunction());
+  }
+
+  @Test
+  public void toRuleRestResponse_shouldReturnNullFields_whenRuleIsEmpty() {
+    RuleDto dto = new RuleDto().setRuleKey("key").setRepositoryKey("repoKey").setStatus(RuleStatus.READY).setType(RuleType.BUG.getDbConstant());
+    RuleRestResponse ruleRestResponse = ruleRestResponseGenerator.toRuleRestResponse(new RuleInformation(dto, List.of()));
+    assertThat(ruleRestResponse.cleanCodeAttribute()).isNull();
+    assertThat(ruleRestResponse.cleanCodeAttributeCategory()).isNull();
+    assertThat(ruleRestResponse.htmlNote()).isNull();
+  }
+
+  @Test
+  public void toRuleRestResponse_shouldReturnParameters_whenParametersAreProvided() {
+    RuleDto dto = RuleTesting.newRule();
+    RuleParamDto ruleParamDto1 = RuleTesting.newRuleParam(dto);
+    RuleParamDto ruleParamDto2 = RuleTesting.newRuleParam(dto);
+    RuleRestResponse ruleRestResponse = ruleRestResponseGenerator.toRuleRestResponse(new RuleInformation(dto, List.of(ruleParamDto1, ruleParamDto2)));
+
+    assertThat(ruleRestResponse.parameters()).extracting(p -> p.key(), p -> p.htmlDescription(), p -> p.defaultValue())
+      .containsExactlyInAnyOrder(tuple(ruleParamDto1.getName(), ruleParamDto1.getDescription(), ruleParamDto1.getDefaultValue()),
+        tuple(ruleParamDto2.getName(), ruleParamDto2.getDescription(), ruleParamDto2.getDefaultValue()));
+  }
+
+  @Test
+  public void toRuleRestResponse_shouldReturnAdhocInformation_whenRuleIsAdhoc() {
+    when(macroInterpreter.interpret(Mockito.anyString())).thenAnswer(invocation -> "interpreted" + invocation.getArgument(0));
+    RuleDto dto = RuleTesting.newRule();
+    dto.setIsAdHoc(true);
+    dto.setAdHocName("adhocName");
+    dto.setAdHocDescription("adhocDescription");
+    dto.setAdHocSeverity(Severity.INFO);
+    dto.setAdHocType(RuleType.BUG.getDbConstant());
+
+    RuleRestResponse ruleRestResponse = ruleRestResponseGenerator.toRuleRestResponse(new RuleInformation(dto, List.of()));
+    assertThat(ruleRestResponse.name()).isEqualTo(dto.getAdHocName());
+    assertThat(ruleRestResponse.descriptionSections()).extracting(r -> r.key(), r -> r.content(), r -> r.context())
+      .containsExactly(tuple("default", "interpreted" + dto.getAdHocDescription(), null));
+    assertThat(ruleRestResponse.severity()).isEqualTo(dto.getAdHocSeverity());
+    assertThat(ruleRestResponse.type().name()).isEqualTo(RuleType.valueOf(dto.getAdHocType()).name());
+  }
+
+  @Test
+  public void toRuleRestResponse_shouldReturnRemediationFunctions() {
+    when(macroInterpreter.interpret(Mockito.anyString())).thenAnswer(invocation -> "interpreted" + invocation.getArgument(0));
+    RuleDto dto = RuleTesting.newRule();
+    dto.setIsAdHoc(true);
+    dto.setAdHocName("adhocName");
+    dto.setAdHocDescription("adhocDescription");
+    dto.setAdHocSeverity(Severity.INFO);
+    dto.setAdHocType(RuleType.BUG.getDbConstant());
+
+    RuleRestResponse ruleRestResponse = ruleRestResponseGenerator.toRuleRestResponse(new RuleInformation(dto, List.of()));
+    assertThat(ruleRestResponse.name()).isEqualTo(dto.getAdHocName());
+    assertThat(ruleRestResponse.descriptionSections()).extracting(r -> r.key(), r -> r.content(), r -> r.context())
+      .containsExactly(tuple("default", "interpreted" + dto.getAdHocDescription(), null));
+    assertThat(ruleRestResponse.severity()).isEqualTo(dto.getAdHocSeverity());
+    assertThat(ruleRestResponse.type().name()).isEqualTo(RuleType.valueOf(dto.getAdHocType()).name());
+  }
+
+}
index 273a96fc718552c66afbe25dbfd409aa67fd2a17..0b0df3218f643fded23618c322fc16b2d70f3fdc 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.core.util.SequenceUuidFactory;
 import org.sonar.core.util.UuidFactory;
 import org.sonar.db.DbTester;
 import org.sonar.db.rule.RuleDto;
+import org.sonar.server.common.text.MacroInterpreter;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.UnauthorizedException;
@@ -38,7 +39,6 @@ import org.sonar.server.rule.RuleCreator;
 import org.sonar.server.rule.RuleDescriptionFormatter;
 import org.sonar.server.rule.index.RuleIndexer;
 import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.text.MacroInterpreter;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.TestResponse;
 import org.sonar.server.ws.WsActionTester;
index 814c5072c7f8db62c618c2c5e322149ec5117b68..57b2e71e64c00e1d96aa8eec2cf5365440de88f4 100644 (file)
@@ -33,11 +33,11 @@ import org.sonar.db.DbTester;
 import org.sonar.db.qualityprofile.QProfileDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleTesting;
+import org.sonar.server.common.text.MacroInterpreter;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.language.LanguageTesting;
 import org.sonar.server.rule.RuleDescriptionFormatter;
 import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.text.MacroInterpreter;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
 import org.sonarqube.ws.Rules;
index fe0bdae89e63ee1b425d9c783bbae176338b39d3..e7bda7cdc8c8c01c0a11286d8c794d4f9941065b 100644 (file)
@@ -60,6 +60,7 @@ import org.sonar.db.rule.RuleDescriptionSectionDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleParamDto;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.common.text.MacroInterpreter;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.language.LanguageTesting;
@@ -74,7 +75,6 @@ import org.sonar.server.rule.RuleDescriptionFormatter;
 import org.sonar.server.rule.index.RuleIndex;
 import org.sonar.server.rule.index.RuleIndexer;
 import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.text.MacroInterpreter;
 import org.sonar.server.util.IntegerTypeValidation;
 import org.sonar.server.util.StringTypeValidation;
 import org.sonar.server.util.TypeValidations;
index 1d9720f6fb36d3e94a2667505096b959cf636253..47d6bf61ddfa2a495690ea21fd25bb6ec15526b7 100644 (file)
@@ -40,9 +40,9 @@ import org.sonar.db.rule.RuleDescriptionSectionDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleParamDto;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.common.text.MacroInterpreter;
 import org.sonar.server.rule.RuleDescriptionFormatter;
 import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.text.MacroInterpreter;
 import org.sonar.server.ws.WsActionTester;
 import org.sonarqube.ws.Common;
 import org.sonarqube.ws.Rules;
index 21385ba831cd0f3a06a52474478604a47bb4bf8c..72d2e7ad1d369b4a3701d7cef7f68b4420a4b331 100644 (file)
@@ -32,6 +32,7 @@ import org.sonar.db.DbTester;
 import org.sonar.db.rule.RuleDescriptionSectionDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.common.text.MacroInterpreter;
 import org.sonar.server.es.EsClient;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.ForbiddenException;
@@ -40,7 +41,6 @@ import org.sonar.server.rule.RuleDescriptionFormatter;
 import org.sonar.server.rule.RuleUpdater;
 import org.sonar.server.rule.index.RuleIndexer;
 import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.text.MacroInterpreter;
 import org.sonar.server.ws.TestResponse;
 import org.sonar.server.ws.WsAction;
 import org.sonar.server.ws.WsActionTester;
index 833e375c6370f76db301895011e2c44ee178111c..7ac89ed0bac202199cbedff6341940984970a27f 100644 (file)
@@ -44,9 +44,9 @@ import org.sonar.db.rule.RuleDto.Scope;
 import org.sonar.db.rule.RuleParamDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.markdown.Markdown;
+import org.sonar.server.common.text.MacroInterpreter;
 import org.sonar.server.rule.RuleDescriptionFormatter;
 import org.sonar.server.rule.ws.RulesResponseFormatter.SearchResult;
-import org.sonar.server.text.MacroInterpreter;
 import org.sonarqube.ws.Common;
 import org.sonarqube.ws.Common.RuleScope;
 import org.sonarqube.ws.Rules;
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/text/Macro.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/text/Macro.java
deleted file mode 100644 (file)
index 4b81c5d..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.text;
-
-public interface Macro {
-
-  String getRegex();
-
-  String getReplacement();
-}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/text/MacroInterpreter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/text/MacroInterpreter.java
deleted file mode 100644 (file)
index f45f94c..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.text;
-
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.platform.Server;
-
-import java.util.List;
-
-@ServerSide
-public class MacroInterpreter {
-  private final List<Macro> macros;
-
-  public MacroInterpreter(Server server) {
-    this.macros = List.of(
-      new RuleMacro(server.getContextPath())
-    );
-  }
-
-  public String interpret(String text) {
-    String textReplaced = text;
-    for (Macro macro : macros) {
-      textReplaced = textReplaced.replaceAll(macro.getRegex(), macro.getReplacement());
-    }
-    return textReplaced;
-  }
-}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/text/RuleMacro.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/text/RuleMacro.java
deleted file mode 100644 (file)
index c0d5e34..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.text;
-
-class RuleMacro implements Macro {
-
-  private static final String COLON = "%3A";
-  private final String contextPath;
-
-  RuleMacro(String contextPath) {
-    this.contextPath = contextPath;
-  }
-
-  /**
-   * First parameter is the repository, second one is the rule key. Exemple : {rule:java:ArchitecturalConstraint}
-   */
-  @Override
-  public String getRegex() {
-    return "\\{rule:([a-zA-Z0-9._-]++):([a-zA-Z0-9._-]++)\\}";
-  }
-
-  @Override
-  public String getReplacement() {
-    return "<a href='" + contextPath + "/coding_rules#rule_key=$1" + COLON + "$2'>$2</a>";
-  }
-}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/text/package-info.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/text/package-info.java
deleted file mode 100644 (file)
index 6c434db..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.server.text;
-
-import javax.annotation.ParametersAreNonnullByDefault;
-
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/text/MacroInterpreterTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/text/MacroInterpreterTest.java
deleted file mode 100644 (file)
index d264562..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.text;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.platform.Server;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class MacroInterpreterTest {
-
-  String path = "http://sonar";
-  MacroInterpreter interpreter;
-
-  @Before
-  public void setUp() {
-    Server server = mock(Server.class);
-    when(server.getContextPath()).thenReturn(path);
-    interpreter = new MacroInterpreter(server);
-  }
-
-  @Test
-  public void should_do_nothing_if_no_macro_detected() {
-    String origin = "nothing to do";
-    String result = interpreter.interpret(origin);
-    assertThat(result).isEqualTo(origin);
-  }
-
-  @Test
-  public void should_replace_rule_macro() {
-    // key of repository and rule can contain alphanumeric latin characters, dashes, underscores and dots
-    String ruleKey = "Some_Repo-Key.1:Some_Rule-Key.1";
-    String origin = "See {rule:" + ruleKey + "} for detail.";
-    String result = interpreter.interpret(origin);
-    // colon should be escaped
-    assertThat(result).isEqualTo("See <a href='" + path + "/coding_rules#rule_key=Some_Repo-Key.1%3ASome_Rule-Key.1'>Some_Rule-Key.1</a> for detail.");
-  }
-
-}
index 5429d9857fff21f414ec4104db858a86acee2c10..cafa2315c3aac3b4907c28107c6365ccde4efdd5 100644 (file)
@@ -81,6 +81,7 @@ import org.sonar.server.ce.ws.CeWsModule;
 import org.sonar.server.common.group.service.GroupMembershipService;
 import org.sonar.server.common.group.service.GroupService;
 import org.sonar.server.common.rule.service.RuleService;
+import org.sonar.server.common.text.MacroInterpreter;
 import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.component.ComponentService;
@@ -249,7 +250,6 @@ import org.sonar.server.telemetry.TelemetryClient;
 import org.sonar.server.telemetry.TelemetryDaemon;
 import org.sonar.server.telemetry.TelemetryDataJsonWriter;
 import org.sonar.server.telemetry.TelemetryDataLoaderImpl;
-import org.sonar.server.text.MacroInterpreter;
 import org.sonar.server.ui.PageRepository;
 import org.sonar.server.ui.WebAnalyticsLoaderImpl;
 import org.sonar.server.ui.ws.NavigationWsModule;