Initial commit
This commit is contained in:
@@ -0,0 +1,440 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
|
||||
<!--
|
||||
Checkstyle configuration that checks the Google coding conventions from Google Java Style
|
||||
that can be found at https://google.github.io/styleguide/javaguide.html
|
||||
|
||||
Checkstyle is very configurable. Be sure to read the documentation at
|
||||
http://checkstyle.org (or in your downloaded distribution).
|
||||
|
||||
To completely disable a check, just comment it out or delete it from the file.
|
||||
To suppress certain violations please review suppression filters.
|
||||
|
||||
Authors: Max Vetrenko, Mauryan Kansara, Ruslan Diachenko, Roman Ivanov.
|
||||
-->
|
||||
|
||||
<module name="Checker">
|
||||
|
||||
<property name="charset" value="UTF-8"/>
|
||||
|
||||
<property name="severity" value="${org.checkstyle.google.severity}" default="warning"/>
|
||||
|
||||
<property name="fileExtensions" value="java, properties, xml"/>
|
||||
<!-- Excludes all 'module-info.java' files -->
|
||||
<!-- See https://checkstyle.org/filefilters/index.html -->
|
||||
<module name="BeforeExecutionExclusionFileFilter">
|
||||
<property name="fileNamePattern" value="module\-info\.java$"/>
|
||||
</module>
|
||||
|
||||
<module name="SuppressWarningsFilter"/>
|
||||
|
||||
<!-- https://checkstyle.org/filters/suppressionfilter.html -->
|
||||
<module name="SuppressionFilter">
|
||||
<property name="file" value="${org.checkstyle.google.suppressionfilter.config}"
|
||||
default="checkstyle-suppressions.xml" />
|
||||
<property name="optional" value="true"/>
|
||||
</module>
|
||||
|
||||
<!-- https://checkstyle.org/filters/suppresswithnearbytextfilter.html -->
|
||||
<module name="SuppressWithNearbyTextFilter">
|
||||
<property name="nearbyTextPattern"
|
||||
value="CHECKSTYLE.SUPPRESS\: (\w+) for ([+-]\d+) lines"/>
|
||||
<property name="checkPattern" value="$1"/>
|
||||
<property name="lineRange" value="$2"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for whitespace -->
|
||||
<!-- See http://checkstyle.org/checks/whitespace/index.html -->
|
||||
<module name="FileTabCharacter">
|
||||
<property name="eachLine" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="LineLength">
|
||||
<property name="fileExtensions" value="java"/>
|
||||
<property name="max" value="140"/>
|
||||
<property name="ignorePattern"
|
||||
value="^package.*|^import.*|href\s*=\s*"[^"]*"|http://|https://|ftp://"/>
|
||||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<module name="OuterTypeFilename"/>
|
||||
<module name="IllegalTokenText">
|
||||
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
|
||||
<property name="format"
|
||||
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
|
||||
<property name="message"
|
||||
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
|
||||
</module>
|
||||
<module name="AvoidEscapedUnicodeCharacters">
|
||||
<property name="allowEscapesForControlCharacters" value="true"/>
|
||||
<property name="allowByTailComment" value="true"/>
|
||||
<property name="allowNonPrintableEscapes" value="true"/>
|
||||
</module>
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="OneTopLevelClass"/>
|
||||
<module name="NoLineWrap">
|
||||
<property name="tokens" value="PACKAGE_DEF, IMPORT, STATIC_IMPORT"/>
|
||||
</module>
|
||||
<module name="NeedBraces">
|
||||
<property name="tokens"
|
||||
value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, LITERAL_WHILE"/>
|
||||
</module>
|
||||
<module name="LeftCurly">
|
||||
<property name="id" value="LeftCurlyEol"/>
|
||||
<property name="tokens"
|
||||
value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF,
|
||||
INTERFACE_DEF, LAMBDA, LITERAL_CATCH,
|
||||
LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF,
|
||||
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, METHOD_DEF,
|
||||
OBJBLOCK, STATIC_INIT, RECORD_DEF, COMPACT_CTOR_DEF"/>
|
||||
</module>
|
||||
<module name="LeftCurly">
|
||||
<property name="id" value="LeftCurlyNl"/>
|
||||
<property name="option" value="nl"/>
|
||||
<property name="tokens"
|
||||
value="LITERAL_CASE, LITERAL_DEFAULT"/>
|
||||
</module>
|
||||
<module name="SuppressionXpathSingleFilter">
|
||||
<!-- LITERAL_CASE, LITERAL_DEFAULT are reused in SWITCH_RULE -->
|
||||
<property name="id" value="LeftCurlyNl"/>
|
||||
<property name="query" value="//SWITCH_RULE/SLIST"/>
|
||||
</module>
|
||||
<module name="RightCurly">
|
||||
<property name="id" value="RightCurlySame"/>
|
||||
<property name="tokens"
|
||||
value="LITERAL_TRY, LITERAL_CATCH, LITERAL_IF, LITERAL_ELSE,
|
||||
LITERAL_DO"/>
|
||||
</module>
|
||||
<module name="RightCurly">
|
||||
<property name="id" value="RightCurlyAlone"/>
|
||||
<property name="option" value="alone"/>
|
||||
<property name="tokens"
|
||||
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
|
||||
INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, INTERFACE_DEF, RECORD_DEF,
|
||||
COMPACT_CTOR_DEF, LITERAL_SWITCH, LITERAL_CASE, LITERAL_FINALLY"/>
|
||||
</module>
|
||||
<module name="SuppressionXpathSingleFilter">
|
||||
<!-- suppression is required till https://github.com/checkstyle/checkstyle/issues/7541 -->
|
||||
<property name="id" value="RightCurlyAlone"/>
|
||||
<property name="query" value="//RCURLY[parent::SLIST[count(./*)=1]
|
||||
or preceding-sibling::*[last()][self::LCURLY]]"/>
|
||||
</module>
|
||||
<module name="WhitespaceAfter">
|
||||
<property name="tokens"
|
||||
value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE, LITERAL_RETURN,
|
||||
LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, LITERAL_FINALLY, DO_WHILE, ELLIPSIS,
|
||||
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_CATCH, LAMBDA,
|
||||
LITERAL_YIELD, LITERAL_CASE, LITERAL_WHEN"/>
|
||||
</module>
|
||||
<module name="WhitespaceAround">
|
||||
<property name="allowEmptyConstructors" value="true"/>
|
||||
<property name="allowEmptyLambdas" value="true"/>
|
||||
<property name="allowEmptyMethods" value="true"/>
|
||||
<property name="allowEmptyTypes" value="true"/>
|
||||
<property name="allowEmptyLoops" value="true"/>
|
||||
<property name="allowEmptySwitchBlockStatements" value="true"/>
|
||||
<property name="ignoreEnhancedForColon" value="false"/>
|
||||
<property name="tokens"
|
||||
value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR,
|
||||
BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND,
|
||||
LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY,
|
||||
LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED,
|
||||
LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN,
|
||||
NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR,
|
||||
SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT,
|
||||
TYPE_EXTENSION_AND, LITERAL_WHEN"/>
|
||||
<message key="ws.notFollowed"
|
||||
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks
|
||||
may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
|
||||
<message key="ws.notPreceded"
|
||||
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
|
||||
</module>
|
||||
<module name="SuppressionXpathSingleFilter">
|
||||
<property name="checks" value="WhitespaceAround"/>
|
||||
<property name="query" value="//*[self::LITERAL_IF or self::LITERAL_ELSE or self::STATIC_INIT
|
||||
or self::LITERAL_TRY or self::LITERAL_CATCH]/SLIST[count(./*)=1]
|
||||
| //*[self::STATIC_INIT or self::LITERAL_TRY or self::LITERAL_IF]
|
||||
//*[self::RCURLY][parent::SLIST[count(./*)=1]]"/>
|
||||
</module>
|
||||
<module name="RegexpSinglelineJava">
|
||||
<property name="format" value="\{[ ]+\}"/>
|
||||
<property name="message" value="Empty blocks should have no spaces. Empty blocks
|
||||
may only be represented as '{}' when not part of a
|
||||
multi-block statement (4.1.3)"/>
|
||||
</module>
|
||||
<module name="OneStatementPerLine"/>
|
||||
<module name="MultipleVariableDeclarations"/>
|
||||
<module name="ArrayTypeStyle"/>
|
||||
<module name="MissingSwitchDefault"/>
|
||||
<module name="FallThrough"/>
|
||||
<module name="UpperEll"/>
|
||||
<module name="ModifierOrder"/>
|
||||
<module name="EmptyLineSeparator">
|
||||
<property name="tokens"
|
||||
value="PACKAGE_DEF, IMPORT, STATIC_IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
|
||||
STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF, RECORD_DEF,
|
||||
COMPACT_CTOR_DEF"/>
|
||||
<property name="allowNoEmptyLineBetweenFields" value="true"/>
|
||||
</module>
|
||||
<module name="SeparatorWrap">
|
||||
<property name="id" value="SeparatorWrapDot"/>
|
||||
<property name="tokens" value="DOT"/>
|
||||
<property name="option" value="nl"/>
|
||||
</module>
|
||||
<module name="SeparatorWrap">
|
||||
<property name="id" value="SeparatorWrapComma"/>
|
||||
<property name="tokens" value="COMMA"/>
|
||||
<property name="option" value="EOL"/>
|
||||
</module>
|
||||
<module name="SeparatorWrap">
|
||||
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/259 -->
|
||||
<property name="id" value="SeparatorWrapEllipsis"/>
|
||||
<property name="tokens" value="ELLIPSIS"/>
|
||||
<property name="option" value="EOL"/>
|
||||
</module>
|
||||
<module name="SeparatorWrap">
|
||||
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/258 -->
|
||||
<property name="id" value="SeparatorWrapArrayDeclarator"/>
|
||||
<property name="tokens" value="ARRAY_DECLARATOR"/>
|
||||
<property name="option" value="EOL"/>
|
||||
</module>
|
||||
<module name="SeparatorWrap">
|
||||
<property name="id" value="SeparatorWrapMethodRef"/>
|
||||
<property name="tokens" value="METHOD_REF"/>
|
||||
<property name="option" value="nl"/>
|
||||
</module>
|
||||
<module name="PackageName">
|
||||
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Package name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="TypeName">
|
||||
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
|
||||
ANNOTATION_DEF, RECORD_DEF"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Type name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="MemberName">
|
||||
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Member name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="ParameterName">
|
||||
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="LambdaParameterName">
|
||||
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="CatchParameterName">
|
||||
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="LocalVariableName">
|
||||
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="PatternVariableName">
|
||||
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Pattern variable name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="ClassTypeParameterName">
|
||||
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Class type name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="RecordComponentName">
|
||||
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Record component name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="RecordTypeParameterName">
|
||||
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Record type name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="MethodTypeParameterName">
|
||||
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Method type name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="InterfaceTypeParameterName">
|
||||
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Interface type name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="NoFinalizer"/>
|
||||
<module name="GenericWhitespace">
|
||||
<message key="ws.followed"
|
||||
value="GenericWhitespace ''{0}'' is followed by whitespace."/>
|
||||
<message key="ws.preceded"
|
||||
value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
|
||||
<message key="ws.illegalFollow"
|
||||
value="GenericWhitespace ''{0}'' should followed by whitespace."/>
|
||||
<message key="ws.notPreceded"
|
||||
value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
|
||||
</module>
|
||||
<module name="Indentation">
|
||||
<property name="basicOffset" value="2"/>
|
||||
<property name="braceAdjustment" value="2"/>
|
||||
<property name="caseIndent" value="2"/>
|
||||
<property name="throwsIndent" value="4"/>
|
||||
<property name="lineWrappingIndentation" value="4"/>
|
||||
<property name="arrayInitIndent" value="2"/>
|
||||
</module>
|
||||
<!-- Suppression for block code until we find a way to detect them properly
|
||||
until https://github.com/checkstyle/checkstyle/issues/15769 -->
|
||||
<module name="SuppressionXpathSingleFilter">
|
||||
<property name="checks" value="Indentation"/>
|
||||
<property name="query" value="//SLIST[not(parent::CASE_GROUP)]/SLIST
|
||||
| //SLIST[not(parent::CASE_GROUP)]/SLIST/RCURLY"/>
|
||||
</module>
|
||||
<module name="AbbreviationAsWordInName">
|
||||
<property name="ignoreFinal" value="false"/>
|
||||
<property name="allowedAbbreviationLength" value="0"/>
|
||||
<property name="tokens"
|
||||
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF,
|
||||
PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF, PATTERN_VARIABLE_DEF, RECORD_DEF,
|
||||
RECORD_COMPONENT_DEF"/>
|
||||
</module>
|
||||
<module name="NoWhitespaceBeforeCaseDefaultColon"/>
|
||||
<module name="OverloadMethodsDeclarationOrder"/>
|
||||
<module name="ConstructorsDeclarationGrouping"/>
|
||||
<module name="VariableDeclarationUsageDistance"/>
|
||||
<module name="CustomImportOrder">
|
||||
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
||||
<property name="separateLineBetweenGroups" value="true"/>
|
||||
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
|
||||
<property name="tokens" value="IMPORT, STATIC_IMPORT, PACKAGE_DEF"/>
|
||||
</module>
|
||||
<module name="MethodParamPad">
|
||||
<property name="tokens"
|
||||
value="CTOR_DEF, LITERAL_NEW, METHOD_CALL, METHOD_DEF, CTOR_CALL,
|
||||
SUPER_CTOR_CALL, ENUM_CONSTANT_DEF, RECORD_DEF, RECORD_PATTERN_DEF"/>
|
||||
</module>
|
||||
<module name="NoWhitespaceBefore">
|
||||
<property name="tokens"
|
||||
value="COMMA, SEMI, POST_INC, POST_DEC, DOT,
|
||||
LABELED_STAT, METHOD_REF"/>
|
||||
<property name="allowLineBreaks" value="true"/>
|
||||
</module>
|
||||
<module name="ParenPad">
|
||||
<property name="tokens"
|
||||
value="ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_CALL, CTOR_DEF, DOT, ENUM_CONSTANT_DEF,
|
||||
EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW,
|
||||
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL,
|
||||
METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA,
|
||||
RECORD_DEF, RECORD_PATTERN_DEF"/>
|
||||
</module>
|
||||
<module name="OperatorWrap">
|
||||
<property name="option" value="NL"/>
|
||||
<property name="tokens"
|
||||
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
|
||||
LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF,
|
||||
TYPE_EXTENSION_AND "/>
|
||||
</module>
|
||||
<module name="AnnotationLocation">
|
||||
<property name="id" value="AnnotationLocationMostCases"/>
|
||||
<property name="tokens"
|
||||
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF,
|
||||
RECORD_DEF, COMPACT_CTOR_DEF"/>
|
||||
</module>
|
||||
<module name="AnnotationLocation">
|
||||
<property name="id" value="AnnotationLocationVariables"/>
|
||||
<property name="tokens" value="VARIABLE_DEF"/>
|
||||
<property name="allowSamelineMultipleAnnotations" value="true"/>
|
||||
</module>
|
||||
<module name="NonEmptyAtclauseDescription"/>
|
||||
<module name="InvalidJavadocPosition"/>
|
||||
<module name="JavadocTagContinuationIndentation"/>
|
||||
<module name="SummaryJavadoc">
|
||||
<property name="forbiddenSummaryFragments"
|
||||
value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
|
||||
</module>
|
||||
<module name="JavadocParagraph">
|
||||
<property name="allowNewlineParagraph" value="false"/>
|
||||
</module>
|
||||
<module name="RequireEmptyLineBeforeBlockTagGroup"/>
|
||||
<module name="AtclauseOrder">
|
||||
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
|
||||
<property name="target"
|
||||
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
|
||||
</module>
|
||||
<module name="JavadocMethod">
|
||||
<property name="accessModifiers" value="public"/>
|
||||
<property name="allowMissingParamTags" value="true"/>
|
||||
<property name="allowMissingReturnTag" value="true"/>
|
||||
<property name="allowedAnnotations" value="Override, Test"/>
|
||||
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, COMPACT_CTOR_DEF"/>
|
||||
</module>
|
||||
<module name="MissingJavadocMethod">
|
||||
<property name="scope" value="protected"/>
|
||||
<property name="allowMissingPropertyJavadoc" value="true"/>
|
||||
<property name="allowedAnnotations" value="Override, Test"/>
|
||||
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF,
|
||||
COMPACT_CTOR_DEF"/>
|
||||
</module>
|
||||
<module name="SuppressionXpathSingleFilter">
|
||||
<property name="checks" value="MissingJavadocMethod"/>
|
||||
<property name="query" value="//*[self::METHOD_DEF or self::CTOR_DEF
|
||||
or self::ANNOTATION_FIELD_DEF or self::COMPACT_CTOR_DEF]
|
||||
[ancestor::*[self::INTERFACE_DEF or self::CLASS_DEF
|
||||
or self::RECORD_DEF or self::ENUM_DEF]
|
||||
[not(./MODIFIERS/LITERAL_PUBLIC)]]"/>
|
||||
</module>
|
||||
<module name="MissingJavadocType">
|
||||
<property name="scope" value="protected"/>
|
||||
<property name="tokens"
|
||||
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
|
||||
RECORD_DEF, ANNOTATION_DEF"/>
|
||||
<property name="excludeScope" value="nothing"/>
|
||||
</module>
|
||||
<module name="MethodName">
|
||||
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Method name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="SuppressionXpathSingleFilter">
|
||||
<property name="checks" value="MethodName"/>
|
||||
<property name="query" value="//METHOD_DEF[
|
||||
./MODIFIERS/ANNOTATION//IDENT[contains(@text, 'Test')]
|
||||
]/IDENT"/>
|
||||
<property name="message" value="'[a-z][a-z0-9][a-zA-Z0-9]*(?:_[a-z][a-z0-9][a-zA-Z0-9]*)*'"/>
|
||||
</module>
|
||||
<module name="SingleLineJavadoc"/>
|
||||
<module name="EmptyCatchBlock">
|
||||
<property name="exceptionVariableName" value="expected"/>
|
||||
</module>
|
||||
<module name="CommentsIndentation">
|
||||
<property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN"/>
|
||||
</module>
|
||||
<!-- https://checkstyle.org/filters/suppressionxpathfilter.html -->
|
||||
<module name="SuppressionXpathFilter">
|
||||
<property name="file" value="${org.checkstyle.google.suppressionxpathfilter.config}"
|
||||
default="checkstyle-xpath-suppressions.xml" />
|
||||
<property name="optional" value="true"/>
|
||||
</module>
|
||||
<module name="SuppressWarningsHolder" />
|
||||
<module name="SuppressionCommentFilter">
|
||||
<property name="offCommentFormat" value="CHECKSTYLE.OFF\: ([\w\|]+)" />
|
||||
<property name="onCommentFormat" value="CHECKSTYLE.ON\: ([\w\|]+)" />
|
||||
<property name="checkFormat" value="$1" />
|
||||
</module>
|
||||
<module name="SuppressWithNearbyCommentFilter">
|
||||
<property name="commentFormat" value="CHECKSTYLE.SUPPRESS\: ([\w\|]+)"/>
|
||||
<!-- $1 refers to the first match group in the regex defined in commentFormat -->
|
||||
<property name="checkFormat" value="$1"/>
|
||||
<!-- The check is suppressed in the next line of code after the comment -->
|
||||
<property name="influenceFormat" value="1"/>
|
||||
</module>
|
||||
</module>
|
||||
</module>
|
||||
@@ -0,0 +1,163 @@
|
||||
package codes.thischwa.cf;
|
||||
|
||||
import codes.thischwa.cf.model.AbstractEntity;
|
||||
import codes.thischwa.cf.model.AbstractResponse;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpDelete;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPatch;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPost;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPut;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClients;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.ContentType;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.io.entity.EntityUtils;
|
||||
import org.apache.hc.core5.http.io.entity.StringEntity;
|
||||
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
|
||||
|
||||
/**
|
||||
* Abstract base class for creating HTTP clients to interact with the Cloudflare API. Provides
|
||||
* methods for handling GET and POST requests and includes utilities for constructing HTTP clients,
|
||||
* managing authentication, and handling JSON serialization.
|
||||
*/
|
||||
@Slf4j
|
||||
abstract class CfBasicHttpClient {
|
||||
private final String baseUrl;
|
||||
private final String authEmail;
|
||||
private final String authKey;
|
||||
private final String authToken;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
CfBasicHttpClient(String baseUrl, String authEmail, String authKey, String authToken) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.authEmail = authEmail;
|
||||
this.authKey = authKey;
|
||||
this.authToken = authToken;
|
||||
this.objectMapper = initObjectMapper();
|
||||
}
|
||||
|
||||
private ObjectMapper initObjectMapper() {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
private CloseableHttpClient createHttpClient() {
|
||||
return HttpClients.custom()
|
||||
.addRequestInterceptorFirst(
|
||||
(request, context, execChain) -> {
|
||||
request.addHeader(HttpHeaders.ACCEPT_CHARSET, "UTF-8");
|
||||
request.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
|
||||
request.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.getMimeType());
|
||||
request.addHeader(
|
||||
HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
|
||||
request.addHeader("X-Auth-Email", authEmail);
|
||||
request.addHeader("X-Auth-Key", authKey);
|
||||
request.addHeader("X-Auth-Token", authToken);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private <T extends AbstractResponse> T executeRequest(
|
||||
ClassicHttpRequest request, Class<T> responseType) throws CloudflareApiException {
|
||||
String logUri = null;
|
||||
try (CloseableHttpClient client = createHttpClient()) {
|
||||
ResultWrapper result =
|
||||
client.execute(
|
||||
request,
|
||||
(ClassicHttpResponse response) ->
|
||||
new ResultWrapper(
|
||||
response.getCode(),
|
||||
EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8)));
|
||||
|
||||
logUri = request.getRequestUri();
|
||||
if (result.statusCode >= 200 && result.statusCode < 300) {
|
||||
return objectMapper.readValue(result.responseBody, responseType);
|
||||
} else {
|
||||
log.error(
|
||||
"{} request failed for URL {}: Status {}",
|
||||
request.getMethod(),
|
||||
request.getUri(),
|
||||
result.statusCode);
|
||||
throw new CloudflareApiException(
|
||||
request.getMethod() + " request failed with status code: " + result.statusCode);
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("JSON parsing error for request to {}", logUri, e);
|
||||
throw new CloudflareApiException("Error processing JSON response", e);
|
||||
} catch (Exception e) {
|
||||
log.error("Error during request execution", e);
|
||||
throw new CloudflareApiException("Request failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sends a GET request to the given endpoint and maps the response. */
|
||||
protected <T extends AbstractResponse> T getRequest(String endpoint, Class<T> responseType)
|
||||
throws CloudflareApiException {
|
||||
HttpGet request = new HttpGet(buildUrl(endpoint));
|
||||
return executeRequest(request, responseType);
|
||||
}
|
||||
|
||||
/** Sends a DELETE request to the given endpoint and maps the response. */
|
||||
protected <T extends AbstractResponse> T deleteRequest(String endpoint, Class<T> responseType)
|
||||
throws CloudflareApiException {
|
||||
HttpDelete request = new HttpDelete(buildUrl(endpoint));
|
||||
return executeRequest(request, responseType);
|
||||
}
|
||||
|
||||
/** Sends a POST request with a payload to the given endpoint and maps the response. */
|
||||
protected <T extends AbstractResponse, R extends AbstractEntity> T postRequest(
|
||||
String endpoint, R requestPayload, Class<T> responseType) throws CloudflareApiException {
|
||||
HttpPost request = new HttpPost(buildUrl(endpoint));
|
||||
setRequestPayload(request, requestPayload);
|
||||
return executeRequest(request, responseType);
|
||||
}
|
||||
|
||||
/** Sends a PUT request with a payload to the given endpoint and maps the response. */
|
||||
protected <T extends AbstractResponse, R extends AbstractEntity> T putRequest(
|
||||
String endpoint, R requestPayload, Class<T> responseType) throws CloudflareApiException {
|
||||
HttpPut request = new HttpPut(buildUrl(endpoint));
|
||||
setRequestPayload(request, requestPayload);
|
||||
return executeRequest(request, responseType);
|
||||
}
|
||||
|
||||
/** Sends a PATCH request with a payload to the given endpoint and maps the response. */
|
||||
protected <T extends AbstractResponse, R extends AbstractEntity> T patchRequest(
|
||||
String endpoint, R requestPayload, Class<T> responseType) throws CloudflareApiException {
|
||||
HttpPatch request = new HttpPatch(buildUrl(endpoint));
|
||||
setRequestPayload(request, requestPayload);
|
||||
return executeRequest(request, responseType);
|
||||
}
|
||||
|
||||
/** Sets the JSON payload for a request. */
|
||||
private <R extends AbstractEntity> void setRequestPayload(
|
||||
BasicClassicHttpRequest request, R requestPayload) throws CloudflareApiException {
|
||||
try {
|
||||
request.setEntity(
|
||||
new StringEntity(
|
||||
objectMapper.writeValueAsString(requestPayload), ContentType.APPLICATION_JSON));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new CloudflareApiException("Error serializing JSON payload", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildUrl(String endpoint) {
|
||||
return baseUrl + endpoint;
|
||||
}
|
||||
|
||||
private record ResultWrapper(int statusCode, String responseBody) {}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
package codes.thischwa.cf;
|
||||
|
||||
import codes.thischwa.cf.model.AbstractResponse;
|
||||
import codes.thischwa.cf.model.PagingRequest;
|
||||
import codes.thischwa.cf.model.RecordEntity;
|
||||
import codes.thischwa.cf.model.RecordMultipleResponse;
|
||||
import codes.thischwa.cf.model.RecordSingleResponse;
|
||||
import codes.thischwa.cf.model.RecordType;
|
||||
import codes.thischwa.cf.model.ZoneEntity;
|
||||
import codes.thischwa.cf.model.ZoneMultipleResponse;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* CfDnsClient is a client interface to interact with Cloudflare DNS service. It allows managing DNS
|
||||
* records and zones within the Cloudflare system, including creating, updating, retrieving, and
|
||||
* deleting DNS records.
|
||||
*
|
||||
* <p>Example:
|
||||
* <pre><code>
|
||||
* // Create a new CfDnsClient instance
|
||||
* CfDnsClient client = new CfDnsClient(
|
||||
* "email@example.com",
|
||||
* "yourApiKey",
|
||||
* "yourApiToken"
|
||||
* );
|
||||
*
|
||||
* // Retrieve a zone
|
||||
* ZoneEntity zone = cfDnsClient.zoneInfo("example.com");
|
||||
* System.out.println("Zone ID: " + zone.getId());
|
||||
*
|
||||
* // Retrieve records of a zone
|
||||
* List<RecordEntity> records = cfDnsClient.sldListAll(zone, "sld");
|
||||
* records.forEach(record ->
|
||||
* System.out.println("Record Type: " + record.getType() + ", Value: " + record.getContent())
|
||||
* );
|
||||
* </code></pre>
|
||||
*/
|
||||
@Setter
|
||||
@Slf4j
|
||||
public class CfDnsClient extends CfBasicHttpClient {
|
||||
private static final String DEFAULT_BASEURL = "https://api.cloudflare.com/client/v4";
|
||||
|
||||
private boolean emptyResultThrowsException;
|
||||
|
||||
/**
|
||||
* Constructs a CfDnsClient instance for interacting with the Cloudflare DNS API.
|
||||
*
|
||||
* @param authEmail The email address associated with the Cloudflare account, used for
|
||||
* authentication.
|
||||
* @param authKey The API key of the Cloudflare account, used as part of the authentication
|
||||
* process.
|
||||
* @param authToken The API token for accessing specific resources within the Cloudflare account.
|
||||
*/
|
||||
public CfDnsClient(String authEmail, String authKey, String authToken) {
|
||||
this(DEFAULT_BASEURL, authEmail, authKey, authToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a CfDnsClient instance for interacting with the Cloudflare DNS API.
|
||||
*
|
||||
* @param baseUrl The base URL of the Cloudflare API to be used for requests.
|
||||
* @param authEmail The email address associated with the Cloudflare account, used for
|
||||
* authentication.
|
||||
* @param authKey The API key of the Cloudflare account, used as part of the authentication
|
||||
* process.
|
||||
* @param authToken The API token for accessing specific resources within the Cloudflare account.
|
||||
*/
|
||||
public CfDnsClient(String baseUrl, String authEmail, String authKey, String authToken) {
|
||||
this(true, baseUrl, authEmail, authKey, authToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@code CfDnsClient}, which facilitates interactions with the
|
||||
* Cloudflare DNS API.
|
||||
*
|
||||
* @param emptyResultThrowsException Specifies if an exception should be thrown when the API
|
||||
* response is empty. Default is true.
|
||||
* @param baseUrl The base URL for the Cloudflare API endpoint.
|
||||
* @param authEmail The email associated with the Cloudflare account for authentication.
|
||||
* @param authKey The API key for authenticating the client with Cloudflare services.
|
||||
* @param authToken The authentication token used for authorized access to Cloudflare API.
|
||||
*/
|
||||
public CfDnsClient(
|
||||
boolean emptyResultThrowsException,
|
||||
String baseUrl,
|
||||
String authEmail,
|
||||
String authKey,
|
||||
String authToken) {
|
||||
super(baseUrl, authEmail, authKey, authToken);
|
||||
this.emptyResultThrowsException = emptyResultThrowsException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of all zones from the Cloudflare API. <br>
|
||||
* This method sends a GET request to the Cloudflare API endpoint for listing zones, processes the
|
||||
* response, and returns the resulting list of ZoneEntity objects.
|
||||
*
|
||||
* @return A list of ZoneEntity objects representing the zones retrieved from the Cloudflare API.
|
||||
* @throws CloudflareApiException If an error occurs during the API request or response handling.
|
||||
*/
|
||||
public List<ZoneEntity> zoneListAll() throws CloudflareApiException {
|
||||
return zoneListAll(PagingRequest.defaultPaging());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of all zones from the Cloudflare API. <br>
|
||||
* This method sends a GET request to the Cloudflare API endpoint for listing zones, processes the
|
||||
* response, and returns the resulting list of ZoneEntity objects.
|
||||
*
|
||||
* @return A list of ZoneEntity objects representing the zones retrieved from the Cloudflare API.
|
||||
* @throws CloudflareApiException If an error occurs during the API request or response handling.
|
||||
*/
|
||||
public List<ZoneEntity> zoneListAll(PagingRequest pagingRequest) throws CloudflareApiException {
|
||||
String endpoint = pagingRequest.addQueryString(CfRequest.ZONE_LIST.buildPath());
|
||||
ZoneMultipleResponse response = getRequest(endpoint, ZoneMultipleResponse.class);
|
||||
checkResponse(response);
|
||||
return response.getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves detailed information about a specific zone by its name.
|
||||
*
|
||||
* @param name The name of the zone to retrieve information for.
|
||||
* @return A {@link ZoneEntity} object that contains details of the specified zone.
|
||||
* @throws CloudflareApiException If an error occurs while making the API request or processing
|
||||
* the response.
|
||||
*/
|
||||
public ZoneEntity zoneInfo(String name) throws CloudflareApiException {
|
||||
String endpoint = CfRequest.ZONE_INFO.buildPath(name);
|
||||
ZoneMultipleResponse response = getRequest(endpoint, ZoneMultipleResponse.class);
|
||||
checkResponse(response, true);
|
||||
return response.getResult().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all record entities for a specific second-level domain (SLD) within a given DNS zone.
|
||||
*
|
||||
* @param zone The DNS zone entity for which the SLD records are to be fetched.
|
||||
* @param sld The second-level domain name for which the records are retrieved.
|
||||
* @return A list of {@code RecordEntity} objects representing the DNS records associated with the
|
||||
* provided SLD.
|
||||
* @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API.
|
||||
*/
|
||||
public List<RecordEntity> sldListAll(ZoneEntity zone, String sld) throws CloudflareApiException {
|
||||
return sldListAll(zone, sld, PagingRequest.defaultPaging());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all record entities for a specific second-level domain (SLD) within a given DNS zone.
|
||||
*
|
||||
* @param zone The DNS zone entity for which the SLD records are to be fetched.
|
||||
* @param sld The second-level domain name for which the records are retrieved.
|
||||
* @param pagingRequest The paging request.
|
||||
* @return A list of {@code RecordEntity} objects representing the DNS records associated with the
|
||||
* provided SLD.
|
||||
* @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API.
|
||||
*/
|
||||
public List<RecordEntity> sldListAll(ZoneEntity zone, String sld, PagingRequest pagingRequest)
|
||||
throws CloudflareApiException {
|
||||
String fqdn = sld + "." + zone.getName();
|
||||
String endpoint =
|
||||
pagingRequest.addQueryString(CfRequest.RECORD_INFO_NAME.buildPath(zone.getId(), fqdn));
|
||||
RecordMultipleResponse resp = getRequest(endpoint, RecordMultipleResponse.class);
|
||||
checkResponse(resp);
|
||||
return resp.getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves detailed information about a specific second-level domain (SLD) record for a given
|
||||
* zone and record type from the Cloudflare API.
|
||||
*
|
||||
* @param zone the zone entity that contains information about the DNS zone
|
||||
* @param sld the second-level domain (SLD) for which the record information is requested
|
||||
* @param type the type of DNS record (e.g., A, AAAA, CNAME) being queried
|
||||
* @return the record entity containing detailed information about the requested SLD and record
|
||||
* type
|
||||
* @throws CloudflareApiException if an error occurs during interaction with the Cloudflare API
|
||||
*/
|
||||
public RecordEntity sldInfo(ZoneEntity zone, String sld, RecordType type)
|
||||
throws CloudflareApiException {
|
||||
String fqdn = sld + "." + zone.getName();
|
||||
String endpoint = CfRequest.RECORD_INFO_NAME_TYPE.buildPath(zone.getId(), fqdn, type);
|
||||
RecordMultipleResponse resp = getRequest(endpoint, RecordMultipleResponse.class);
|
||||
checkResponse(resp, true);
|
||||
return resp.getResult().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DNS record in the specified zone using the Cloudflare API.
|
||||
*
|
||||
* @param zone The zone entity where the record will be created. Contains details such as zone ID.
|
||||
* @param rec The record entity representing the DNS record to be created, including its
|
||||
* attributes.
|
||||
* @return The created record entity as returned by the Cloudflare API.
|
||||
* @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API.
|
||||
*/
|
||||
public RecordEntity recordCreate(ZoneEntity zone, RecordEntity rec)
|
||||
throws CloudflareApiException {
|
||||
String endpoint = CfRequest.RECORD_CREATE.buildPath(zone.getId());
|
||||
RecordSingleResponse resp = postRequest(endpoint, rec, RecordSingleResponse.class);
|
||||
checkResponse(resp);
|
||||
return resp.getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a DNS record of the specified type within a given zone on the Cloudflare API.
|
||||
*
|
||||
* @param zone The zone entity that specifies the zone in which the record exists.
|
||||
* @param rec The record entity that represents the DNS record to be deleted.
|
||||
* @return {@code true} if the DNS record was successfully deleted; {@code false} otherwise.
|
||||
* @throws CloudflareApiException if there is an issue during the API communication or the request
|
||||
* fails for any reason.
|
||||
*/
|
||||
public boolean recordDelete(ZoneEntity zone, RecordEntity rec) throws CloudflareApiException {
|
||||
boolean changed = recordDelete(zone, rec.getId());
|
||||
if (changed) {
|
||||
log.info("Record {} of the type {} successful deleted.", rec.getName(), rec.getType());
|
||||
} else {
|
||||
log.warn("Record {} of the type {} was not deleted.", rec.getName(), rec.getType());
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a DNS record of the specified type within a given zone on the Cloudflare API.
|
||||
*
|
||||
* @param zone The zone entity that specifies the zone in which the record exists.
|
||||
* @param id The record entity that represents the DNS record to be deleted.
|
||||
* @return {@code true} if the DNS record was successfully deleted; {@code false} otherwise.
|
||||
* @throws CloudflareApiException if there is an issue during the API communication or the request
|
||||
* fails for any reason.
|
||||
*/
|
||||
public boolean recordDelete(ZoneEntity zone, String id) throws CloudflareApiException {
|
||||
String endpoint = CfRequest.RECORD_DELETE.buildPath(zone.getId(), id);
|
||||
RecordSingleResponse resp = deleteRequest(endpoint, RecordSingleResponse.class);
|
||||
checkResponse(resp);
|
||||
return resp.getResult().getId().equals(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing DNS record in a specified Cloudflare zone.
|
||||
*
|
||||
* @param zone the zone entity containing the ID of the target zone
|
||||
* @param rec the record entity containing the ID of the DNS record to be updated and its updated
|
||||
* data
|
||||
* @return the updated record entity as returned by the Cloudflare API
|
||||
* @throws CloudflareApiException if an error occurs while interacting with the Cloudflare API
|
||||
*/
|
||||
public RecordEntity recordUpdate(ZoneEntity zone, RecordEntity rec)
|
||||
throws CloudflareApiException {
|
||||
// reset all dates, it causes an API issue
|
||||
rec.setModifiedOn(null);
|
||||
rec.setCreatedOn(null);
|
||||
String endpoint = CfRequest.RECORD_UPDATE.buildPath(zone.getId(), rec.getId());
|
||||
RecordSingleResponse resp = patchRequest(endpoint, rec, RecordSingleResponse.class);
|
||||
checkResponse(resp);
|
||||
return resp.getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to delete a DNS record of a specific type for a given zone and second-level domain
|
||||
* (SLD), if it exists.
|
||||
*
|
||||
* @param zone The zone in which the DNS record resides. It provides information about the domain.
|
||||
* @param sld The second-level domain (SLD) of the fully qualified domain name (FQDN) for which
|
||||
* the record is being deleted.
|
||||
* @param type The type of the DNS record to be deleted (e.g., A, CNAME, TXT).
|
||||
* @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API.
|
||||
*/
|
||||
public void recordDeleteTypeIfExists(ZoneEntity zone, String sld, RecordType type)
|
||||
throws CloudflareApiException {
|
||||
String fqdn = sld + "." + zone.getName();
|
||||
try {
|
||||
RecordEntity rec = sldInfo(zone, sld, type);
|
||||
recordDelete(zone, rec);
|
||||
log.debug("Record {} of type {} successful deleted.", fqdn, type);
|
||||
} catch (CloudflareNotFoundException e) {
|
||||
log.debug("Record {} of type {} does not exist.", fqdn, type);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkResponse(AbstractResponse resp) throws CloudflareApiException {
|
||||
checkResponse(resp, false);
|
||||
}
|
||||
|
||||
private void checkResponse(AbstractResponse resp, boolean singleResultExpected)
|
||||
throws CloudflareApiException {
|
||||
if (!resp.isSuccess()) {
|
||||
String errors =
|
||||
resp.getErrors().stream().map(Object::toString).collect(Collectors.joining(", "));
|
||||
throw new CloudflareApiException("Error in response: " + errors);
|
||||
}
|
||||
|
||||
if (resp instanceof RecordMultipleResponse respMulti) {
|
||||
if (singleResultExpected && respMulti.getResultInfo().getTotalCount() > 1) {
|
||||
throw new CloudflareApiException(
|
||||
"Unexpected result count: " + respMulti.getResultInfo().getTotalCount());
|
||||
}
|
||||
if (emptyResultThrowsException && respMulti.getResultInfo().getTotalCount() == 0) {
|
||||
throw new CloudflareNotFoundException("No result found");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package codes.thischwa.cf;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Enum CfRequest encapsulates various API endpoint paths for managing DNS zones and records in a
|
||||
* cohesive and reusable manner. Each enum constant represents a specific API request path.
|
||||
*/
|
||||
@Getter
|
||||
public enum CfRequest {
|
||||
|
||||
// for handling zones
|
||||
ZONE_LIST("/zones"),
|
||||
ZONE_INFO("/zones?name=%s"),
|
||||
|
||||
// for handling records
|
||||
RECORD_CREATE("/zones/%s/dns_records"),
|
||||
RECORD_INFO_NAME("/zones/%s/dns_records?name=%s"),
|
||||
RECORD_INFO_NAME_TYPE("/zones/%s/dns_records?name=%s&type=%s"),
|
||||
RECORD_UPDATE("/zones/%s/dns_records/%s"),
|
||||
RECORD_DELETE("/zones/%s/dns_records/%s");
|
||||
|
||||
private static final char varIdentification = '%';
|
||||
private final String path;
|
||||
|
||||
CfRequest(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the complete API endpoint path by formatting the base path with the provided
|
||||
* arguments.
|
||||
*
|
||||
* @param vars the arguments to format the path string with; these are typically specific
|
||||
* identifiers or parameters required by the API endpoint.
|
||||
* @return the fully constructed API endpoint path as a string.
|
||||
*/
|
||||
String buildPath(Object... vars) {
|
||||
long varCount = path.chars().filter(c -> c == varIdentification).count();
|
||||
if (varCount != vars.length) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"The number of variables (%d) does not match the number of parameters (%d) in the path string: %s",
|
||||
vars.length, varCount, path));
|
||||
}
|
||||
return String.format(path, vars);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package codes.thischwa.cf;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* Represents a custom exception for errors encountered while interacting with the Cloudflare API.
|
||||
*/
|
||||
public class CloudflareApiException extends Exception {
|
||||
|
||||
@Serial private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs a new CloudflareApiException with the specified detail message.
|
||||
*
|
||||
* @param message the detail message, which provides more information about the exception.
|
||||
*/
|
||||
public CloudflareApiException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new CloudflareApiException with the specified detail message and cause.
|
||||
*
|
||||
* @param message the detail message, which provides additional context or information about the exception.
|
||||
* @param cause the cause of this exception, which is the underlying throwable that triggered this exception.
|
||||
*/
|
||||
public CloudflareApiException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new CloudflareApiException with the specified cause.
|
||||
*
|
||||
* @param cause the cause of this exception, which is the underlying throwable
|
||||
* that triggered this exception.
|
||||
*/
|
||||
public CloudflareApiException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package codes.thischwa.cf;
|
||||
|
||||
/**
|
||||
* This exception is thrown to indicate that a requested resource was not found during interaction
|
||||
* with the Cloudflare API.
|
||||
*
|
||||
* <p>It extends {@link CloudflareApiException} to provide specific errors related to situations
|
||||
* where Cloudflare responds with a "not found" operation.
|
||||
*/
|
||||
public class CloudflareNotFoundException extends CloudflareApiException {
|
||||
|
||||
/**
|
||||
* Constructs a new CloudflareNotFoundException with the specified detail message.
|
||||
*
|
||||
* @param message the detail message, which provides additional context about the "not found" error
|
||||
* encountered during interaction with the Cloudflare API.
|
||||
*/
|
||||
public CloudflareNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new CloudflareNotFoundException with the specified detail message and cause.
|
||||
*
|
||||
* @param message the detail message, which provides additional context about the "not found" error
|
||||
* encountered during interaction with the Cloudflare API.
|
||||
* @param cause the cause of this exception, which is the underlying throwable that triggered this exception.
|
||||
*/
|
||||
public CloudflareNotFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new CloudflareNotFoundException with the specified cause.
|
||||
*
|
||||
* @param cause the cause of this exception, which is the underlying throwable
|
||||
* that triggered this exception.
|
||||
*/
|
||||
public CloudflareNotFoundException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Represents a base abstract entity class for modeling domain objects with a unique identifier.
|
||||
*
|
||||
* <p>This class provides a fundamental contract for entities by implementing the {@link
|
||||
* ResponseEntity} interface. The primary attribute of this class is the `id` field, which serves as
|
||||
* a unique identifier for all derived entities.
|
||||
*
|
||||
* <p>Commonly extended by other entity classes to maintain a consistent entity structure across the
|
||||
* domain models. This encourages code reusability and consistency within the system.
|
||||
*/
|
||||
@Data
|
||||
public class AbstractEntity implements ResponseEntity {
|
||||
private String id;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Abstract base class for response models that contain multiple result entries.
|
||||
*
|
||||
* <p>This class is designed to handle API responses where multiple entities are part of the result
|
||||
* set, such as paginated or batched data. It extends {@link AbstractResponse} to include additional
|
||||
* attributes specific to multi-entity responses.
|
||||
*
|
||||
* <p>Attributes:
|
||||
*
|
||||
* <ul>
|
||||
* <li>`resultInfo`: Provides metadata about the result set, such as pagination details like page
|
||||
* number, total count, number of results per page, etc.
|
||||
* <li>`result`: A list of entities representing the main body of the response. The type of
|
||||
* entities in the result list is determined by the generic parameter {@code T}, which must
|
||||
* extend {@link ResponseEntity}.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Subclasses can be created by specifying the entity type that the response should handle.
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public abstract class AbstractMultipleResponse<T extends ResponseEntity> extends AbstractResponse {
|
||||
private ResultInfo resultInfo;
|
||||
private List<T> result;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Abstract base class for API response models.
|
||||
*
|
||||
* <p>This class encapsulates common attributes used to represent the result of an API request. It
|
||||
* can be extended to define more specific response structures.
|
||||
*
|
||||
* <p>Attributes:
|
||||
*
|
||||
* <ol>
|
||||
* <li><b>success</b>: Indicates whether the API request was successful.
|
||||
* <li><b>errors</b>: A list of error messages, if any, returned by the API.
|
||||
* <li><b>messages</b>: A list of informational or status messages accompanying the response.
|
||||
* </ol>
|
||||
*
|
||||
* <p>This structure is designed for consistency and ease of extending response models in
|
||||
* applications that require uniform response structures.
|
||||
*/
|
||||
@Data
|
||||
public abstract class AbstractResponse {
|
||||
private boolean success;
|
||||
private List<String> errors;
|
||||
private List<String> messages;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Represents a base abstract response model for handling single response entities within an API
|
||||
* response.
|
||||
*
|
||||
* <p>This class extends {@code AbstractResponse}, inheriting common response attributes such as
|
||||
* success status, error messages, and informational messages. It introduces a single generic type
|
||||
* parameter {@code <T>} which extends {@code ResponseEntity}, enforcing type consistency for the
|
||||
* result attribute.
|
||||
*
|
||||
* <p>Primary Attribute: {@code result}: Represents the single entity result of the response. This
|
||||
* is a generic type that ensures flexibility and adaptability for different entity models.
|
||||
*
|
||||
* @param <T> The type of the response entity that extends {@code ResponseEntity}.
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public abstract class AbstractSingleResponse<T extends ResponseEntity> extends AbstractResponse {
|
||||
private T result;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Represents a request model for paginated data.
|
||||
*
|
||||
* <p>This class encapsulates the page number and the number of items per page for a paginated
|
||||
* request, along with utility methods for constructing and retrieving pagination parameters.
|
||||
*
|
||||
* <p>Key functionalities:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Creating a {@code PagingRequest} instance with specific pagination values using the {@code
|
||||
* of} method.
|
||||
* <li>Creating a default {@code PagingRequest} with predefined pagination values.
|
||||
* <li>Retrieving pagination parameters as a key-value map.
|
||||
* <li>Generating a query string representation for the pagination parameters.
|
||||
* </ul>
|
||||
*/
|
||||
@Data
|
||||
public class PagingRequest {
|
||||
private int page;
|
||||
private int perPage;
|
||||
|
||||
PagingRequest(int page, int perPage) {
|
||||
this.page = page;
|
||||
this.perPage = perPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code PagingRequest} instance with the specified page number and items per page.
|
||||
*
|
||||
* @param page the page number to be requested
|
||||
* @param perPage the number of items to be included per page
|
||||
* @return a new {@code PagingRequest} instance with the provided parameters
|
||||
*/
|
||||
public static PagingRequest of(int page, int perPage) {
|
||||
return new PagingRequest(page, perPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default {@code PagingRequest} instance with a page number set to 1 and a high number
|
||||
* of items per page (5,000,000) to accommodate large dataset requests.
|
||||
*
|
||||
* @return a default {@code PagingRequest} instance with predefined pagination parameters
|
||||
*/
|
||||
public static PagingRequest defaultPaging() {
|
||||
return new PagingRequest(1, 5000000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the pagination parameters in a key-value map format.
|
||||
*
|
||||
* @return a map containing the pagination parameters, where the key "page" indicates the current
|
||||
* page number and the key "perPage" indicates the number of items per page.
|
||||
*/
|
||||
public Map<String, String> getPagingParams() {
|
||||
return Map.of("page", String.valueOf(page), "perPage", String.valueOf(perPage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a query string with pagination parameters (page and perPage) to the provided endpoint.
|
||||
*
|
||||
* @param endpoint the base URL or API endpoint to which the query string will be appended
|
||||
* @return the complete URL with the appended query string for pagination
|
||||
*/
|
||||
public String addQueryString(String endpoint) {
|
||||
return endpoint + queryString(endpoint.contains("?"));
|
||||
}
|
||||
|
||||
private String queryString(boolean add) {
|
||||
String qs = "page=" + page + "&perPage=" + perPage;
|
||||
return add ? "&" + qs : "?" + qs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a DNS record entity within a specific zone.
|
||||
*
|
||||
* <p>Attributes defined in this class include:
|
||||
*
|
||||
* <ul>
|
||||
* <li>DNS record type such as "A" or "CNAME".
|
||||
* <li>Name of the DNS record.
|
||||
* <li>Content of the DNS record, such as an IP address.
|
||||
* <li>Flags indicating whether the record is proxiable or proxied.
|
||||
* <li>TTL (Time-To-Live) for the DNS record.
|
||||
* <li>A locked status to indicate immutability of the record.
|
||||
* <li>Zone-specific metadata including zone ID and name.
|
||||
* <li>Timestamps for creation and modification.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Provides a static factory method {@code build} for creating a DNS record with specific
|
||||
* attributes.
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class RecordEntity extends AbstractEntity {
|
||||
private String type;
|
||||
private String name;
|
||||
private String content;
|
||||
private Boolean proxiable;
|
||||
private Boolean proxied;
|
||||
private Integer ttl;
|
||||
private Boolean locked;
|
||||
@Nullable private String zoneId;
|
||||
@Nullable private String zoneName;
|
||||
@Nullable private LocalDateTime modifiedOn;
|
||||
@Nullable private LocalDateTime createdOn;
|
||||
|
||||
/**
|
||||
* Builds and returns a {@link RecordEntity} instance with the specified attributes.
|
||||
*
|
||||
* @param name the name of the DNS record
|
||||
* @param type the {@link RecordType} of the DNS record
|
||||
* @param ttl the time-to-live (TTL) value for the DNS record
|
||||
* @param ip the content of the DNS record, typically an IP address
|
||||
* @return a {@link RecordEntity} populated with the provided attributes
|
||||
*/
|
||||
public static RecordEntity build(String name, RecordType type, Integer ttl, String ip) {
|
||||
RecordEntity rec = new RecordEntity();
|
||||
rec.setName(name);
|
||||
rec.setType(type.getType());
|
||||
rec.setTtl(ttl);
|
||||
rec.setContent(ip);
|
||||
return rec;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
/** Represents the API response of the Cloudflare API containing multiple DNS record entities. */
|
||||
public class RecordMultipleResponse extends AbstractMultipleResponse<RecordEntity> {}
|
||||
@@ -0,0 +1,4 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
/** Represents the API response of the Cloudflare API containing a single DNS record entity. */
|
||||
public class RecordSingleResponse extends AbstractSingleResponse<RecordEntity> {}
|
||||
@@ -0,0 +1,46 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Enum representing various DNS record types.
|
||||
*
|
||||
* <p>Each constant in this enum corresponds to a specific DNS record type, such as "A", "AAAA",
|
||||
* "CNAME", or "TXT". This enum provides a means to standardize the representation of these record
|
||||
* types throughout the application while allowing easy retrieval of their string representation.
|
||||
*/
|
||||
@Getter
|
||||
public enum RecordType {
|
||||
A("A"),
|
||||
AAAA("AAAA"),
|
||||
CAA("CAA"),
|
||||
CERT("CERT"),
|
||||
CNAME("CNAME"),
|
||||
DNSKEY("DNSKEY"),
|
||||
DS("DS"),
|
||||
HTTPS("HTTPS"),
|
||||
LOC("LOC"),
|
||||
MX("MX"),
|
||||
NAPTR("NAPTR"),
|
||||
NS("NS"),
|
||||
OPENPGPKEY("OPENPGPKEY"),
|
||||
PTR("PTR"),
|
||||
SMIMEA("SMIMEA"),
|
||||
SRV("SRV"),
|
||||
SSHFP("SSHFP"),
|
||||
SVCB("SVCB"),
|
||||
TLSA("TLSA"),
|
||||
TXT("TXT"),
|
||||
URI("URI");
|
||||
|
||||
private final String type;
|
||||
|
||||
RecordType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getType();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
/**
|
||||
* Represents a contract for entities that have a unique identifier.
|
||||
*
|
||||
* <p>This interface is primarily used as a common abstraction for domain objects that require a
|
||||
* unique identifier, enabling type consistency and code reusability.
|
||||
*/
|
||||
public interface ResponseEntity {
|
||||
|
||||
/**
|
||||
* Retrieves the unique identifier of the entity.
|
||||
*
|
||||
* @return the unique identifier as a String
|
||||
*/
|
||||
String getId();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Represents metadata for paginated results.
|
||||
*
|
||||
* <p>This class contains information about the current page, page size, total pages, and result
|
||||
* counts, which can be utilized in managing and navigating through paginated data.
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>page:</b> The current page number.
|
||||
* <li><b>perPage:</b> The number of results per page.
|
||||
* <li><b>totalPages:</b> The total number of pages available.
|
||||
* <li><b>count:</b> The number of results on the current page.
|
||||
* <li><b>totalCount:</b> The total number of results across all pages.
|
||||
* </ul>
|
||||
*/
|
||||
@Data
|
||||
public class ResultInfo {
|
||||
private int page;
|
||||
private int perPage;
|
||||
private int totalPages;
|
||||
private int count;
|
||||
private int totalCount;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Set;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Represents a DNS zone entity in the Cloudflare DNS system. <br>
|
||||
*
|
||||
* <p>This class encapsulates all relevant data and metadata associated with a zone, including but
|
||||
* not limited to the following attributes:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Zone name.
|
||||
* <li>Development mode status.
|
||||
* <li>Active and original name servers linked to the zone.
|
||||
* <li>Timestamps indicating when the zone was created, modified, or activated.
|
||||
* <li>Current operational status of the zone (e.g., active, inactive).
|
||||
* <li>Boolean flag indicating whether the zone is paused.
|
||||
* <li>Zone type, representing the nature of the DNS zone (e.g., full, partial).
|
||||
* </ul>
|
||||
*
|
||||
* <p>This class extends {@link AbstractEntity} to inherit basic entity properties and to provide a
|
||||
* consistent interface across domain models.
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class ZoneEntity extends AbstractEntity {
|
||||
private String name;
|
||||
private Integer developmentMode;
|
||||
private Set<String> nameServers;
|
||||
private Set<String> originalNameServers;
|
||||
private LocalDateTime createdOn;
|
||||
private LocalDateTime modifiedOn;
|
||||
private LocalDateTime activatedOn;
|
||||
private String status;
|
||||
private Boolean paused;
|
||||
private String type;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
/** Represents a response model that contains multiple {@link ZoneEntity} instances. */
|
||||
public class ZoneMultipleResponse extends AbstractMultipleResponse<ZoneEntity> {}
|
||||
@@ -0,0 +1,2 @@
|
||||
/** The model of CloudflareDNS-java. */
|
||||
package codes.thischwa.cf.model;
|
||||
@@ -0,0 +1,2 @@
|
||||
/** The base package of CloudflareDNS-java. */
|
||||
package codes.thischwa.cf;
|
||||
@@ -0,0 +1,93 @@
|
||||
package codes.thischwa.cf;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import codes.thischwa.cf.model.RecordEntity;
|
||||
import codes.thischwa.cf.model.RecordType;
|
||||
import codes.thischwa.cf.model.ZoneEntity;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
// TODO: #testDns should be clean-up it's test data
|
||||
@Slf4j
|
||||
public class CfClientTest {
|
||||
|
||||
private static final String zoneStr = "mein-d-ns.de";
|
||||
private static final String sldStr = "devsld";
|
||||
private static int ttl = 60;
|
||||
|
||||
private final String email = System.getenv("API_EMAIL");
|
||||
private final String apiKey = System.getenv("API_KEY");
|
||||
private final String apiToken = System.getenv("API_TOKEN");
|
||||
|
||||
private final CfDnsClient client = new CfDnsClient(email, apiKey, apiToken);
|
||||
|
||||
@Test
|
||||
void testList() throws Exception {
|
||||
List<ZoneEntity> zList = client.zoneListAll();
|
||||
assertEquals(1, zList.size());
|
||||
|
||||
List<RecordEntity> rList = client.sldListAll(zList.get(0), "test");
|
||||
assertFalse(rList.isEmpty());
|
||||
|
||||
assertThrows(
|
||||
CloudflareNotFoundException.class, () -> client.sldListAll(zList.get(0), "notexisting"));
|
||||
|
||||
client.setEmptyResultThrowsException(false);
|
||||
rList = client.sldListAll(zList.get(0), "notexisting");
|
||||
assertTrue(rList.isEmpty());
|
||||
client.setEmptyResultThrowsException(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDns() throws Exception {
|
||||
ZoneEntity z = client.zoneInfo(zoneStr);
|
||||
assertEquals("0a83dd6e7f8c46039f2517bbded8115e", z.getId());
|
||||
assertEquals("mein-d-ns.de", z.getName());
|
||||
assertEquals("active", z.getStatus());
|
||||
assertEquals(2, z.getNameServers().size());
|
||||
assertTrue(z.getNameServers().contains("sergi.ns.cloudflare.com"));
|
||||
assertEquals(4, z.getOriginalNameServers().size());
|
||||
assertTrue(z.getOriginalNameServers().contains("a.ns14.net"));
|
||||
assertNotNull(z.getActivatedOn());
|
||||
assertNotNull(z.getModifiedOn());
|
||||
assertNotNull(z.getCreatedOn());
|
||||
assertEquals(LocalDate.of(2025, 1, 20), z.getCreatedOn().toLocalDate());
|
||||
|
||||
RecordEntity r = client.sldInfo(z, "test", RecordType.A);
|
||||
assertEquals("b345fec8769a2980811a8ff901b4e158", r.getId());
|
||||
assertEquals("test.mein-d-ns.de", r.getName());
|
||||
assertEquals("A", r.getType());
|
||||
assertEquals("129.0.0.3", r.getContent());
|
||||
|
||||
RecordEntity createdRe1 =
|
||||
client.recordCreate(
|
||||
z, RecordEntity.build(sldStr + "." + zoneStr, RecordType.A, ttl, "130.0.0.3"));
|
||||
r = client.sldInfo(z, sldStr, RecordType.A);
|
||||
assertEquals("130.0.0.3", r.getContent());
|
||||
RecordEntity createdRe2 =
|
||||
client.recordCreate(
|
||||
z,
|
||||
RecordEntity.build(
|
||||
sldStr + "." + zoneStr, RecordType.AAAA, ttl, "2a0a:4cc0:c0:2e4::1"));
|
||||
r = client.sldInfo(z, sldStr, RecordType.AAAA);
|
||||
assertEquals("2a0a:4cc0:c0:2e4::1", r.getContent());
|
||||
|
||||
createdRe2.setContent("2a0a:4cc0:c0:2e4::2");
|
||||
client.recordUpdate(z, createdRe2);
|
||||
r = client.sldInfo(z, sldStr, RecordType.AAAA);
|
||||
assertEquals("2a0a:4cc0:c0:2e4::2", r.getContent());
|
||||
|
||||
r = client.sldInfo(z, sldStr, RecordType.A);
|
||||
assertEquals("130.0.0.3", r.getContent());
|
||||
assertTrue(client.recordDelete(z, createdRe2));
|
||||
assertThrows(
|
||||
CloudflareNotFoundException.class, () -> client.sldInfo(z, sldStr, RecordType.AAAA));
|
||||
|
||||
client.recordDeleteTypeIfExists(z, sldStr, RecordType.A);
|
||||
assertThrows(CloudflareNotFoundException.class, () -> client.sldInfo(z, sldStr, RecordType.A));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package codes.thischwa.cf;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import codes.thischwa.cf.model.RecordType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class CfRequestTest {
|
||||
|
||||
@Test
|
||||
public void testBuildPathWithSingleVariable() {
|
||||
String result = CfRequest.RECORD_CREATE.buildPath("zone123");
|
||||
assertEquals("/zones/zone123/dns_records", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildPathWithMultipleVariables() {
|
||||
String result = CfRequest.RECORD_UPDATE.buildPath("zone123", "record456");
|
||||
assertEquals("/zones/zone123/dns_records/record456", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildPathWithoutVariables() {
|
||||
String result = CfRequest.ZONE_LIST.buildPath();
|
||||
assertEquals("/zones", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildRecordInfoName() {
|
||||
String result = CfRequest.RECORD_INFO_NAME.buildPath("zone123", "sub.domain.com");
|
||||
assertEquals("/zones/zone123/dns_records?name=sub.domain.com", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildRecordDelete() {
|
||||
String result = CfRequest.RECORD_DELETE.buildPath("zone123", "record789");
|
||||
assertEquals("/zones/zone123/dns_records/record789", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildZoneInfo() {
|
||||
String result = CfRequest.ZONE_INFO.buildPath("zone123");
|
||||
assertEquals("/zones?name=zone123", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildRecordInfo() {
|
||||
String result = CfRequest.RECORD_INFO_NAME_TYPE.buildPath("zone123", "sld.domain.com", RecordType.A);
|
||||
assertEquals("/zones/zone123/dns_records?name=sld.domain.com&type=A", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildPathInvalidArguments() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> CfRequest.RECORD_INFO_NAME_TYPE.buildPath("zone123", "sld.domain.com"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package codes.thischwa.cf.model;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class PagingRequestTest {
|
||||
|
||||
@Test
|
||||
public void testBuildPath() {
|
||||
String result = PagingRequest.defaultPaging().addQueryString("/zones");
|
||||
assertEquals("/zones?page=1&perPage=5000000", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildPathAdditional() {
|
||||
String result = new PagingRequest( 10, 100).addQueryString("/zones?foo=bar");
|
||||
assertEquals("/zones?foo=bar&page=10&perPage=100", result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="false">
|
||||
|
||||
<appender name="current"
|
||||
class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%t] %-5level %logger{50} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="debug">
|
||||
<appender-ref ref="current"/>
|
||||
</root>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user