Skip to content

Commit

Permalink
feat(service): create validate api metadata domain service
Browse files Browse the repository at this point in the history
  • Loading branch information
jourdiw committed Mar 22, 2024
1 parent d711f51 commit dbcfcde
Show file tree
Hide file tree
Showing 6 changed files with 372 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.gravitee.apim.core.api.domain_service;

import io.gravitee.apim.core.DomainService;
import io.gravitee.apim.core.api.exception.DuplicateApiMetadataKeyException;
import io.gravitee.apim.core.api.exception.DuplicateApiMetadataNameException;
import io.gravitee.apim.core.api.exception.InvalidApiMetadataValueException;
import io.gravitee.apim.core.api.model.Api;
import io.gravitee.apim.core.api.query_service.ApiMetadataQueryService;
import io.gravitee.apim.core.membership.domain_service.ApiPrimaryOwnerDomainService;
import io.gravitee.apim.core.metadata.crud_service.MetadataCrudService;
import io.gravitee.apim.core.metadata.model.Metadata;
import io.gravitee.apim.core.metadata.model.MetadataId;
import jakarta.mail.internet.InternetAddress;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Map;
import java.util.Objects;
import lombok.AllArgsConstructor;

@AllArgsConstructor
@DomainService
public class ValidateApiMetadataDomainService {

private final ApiMetadataQueryService metadataQueryService;
private final MetadataCrudService metadataCrudService;
private final ApiPrimaryOwnerDomainService apiPrimaryOwnerDomainService;
private final ApiMetadataDecoderDomainService apiMetadataDecoderDomainService;

private final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");

public void validateUniqueKey(String apiId, String key) {
this.metadataCrudService.findById(
MetadataId.builder().key(key).referenceId(apiId).referenceType(Metadata.ReferenceType.API).build()
)
.ifPresent(m -> {
throw new DuplicateApiMetadataKeyException(apiId, key);
});
}

public void validateUniqueName(String apiId, String name) {
this.metadataQueryService.findApiMetadata(apiId)
.values()
.forEach(val -> {
if (val.getName().equalsIgnoreCase(name)) {
throw new DuplicateApiMetadataNameException(apiId, name);
}
});
}

public void validateValueByFormat(Api api, String organizationId, String value, Metadata.MetadataFormat format) {
var valueToCheck = Objects.nonNull(value) && value.startsWith("${") ? this.getDecodedValue(api, organizationId, value) : value;

try {
switch (format) {
case URL:
new URL(valueToCheck);
break;
case MAIL:
final InternetAddress email = new InternetAddress(valueToCheck);
email.validate();
break;
case DATE:
SIMPLE_DATE_FORMAT.setLenient(false);
SIMPLE_DATE_FORMAT.parse(valueToCheck);
break;
case NUMERIC:
Double.valueOf(valueToCheck);
break;
}
} catch (Exception e) {
throw new InvalidApiMetadataValueException(value, format.name());
}
}

private String getDecodedValue(Api api, String organizationId, String value) {
var apiTemplate = new ApiMetadataDecoderDomainService.ApiMetadataDecodeContext(
api,
Map.of(), // Metadata cannot reference another metadata entry
apiPrimaryOwnerDomainService.getApiPrimaryOwner(organizationId, api.getId())
);

return this.apiMetadataDecoderDomainService.decodeMetadataValue(value, apiTemplate);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.gravitee.apim.core.api.exception;

import io.gravitee.apim.core.exception.ValidationDomainException;

public class DuplicateApiMetadataKeyException extends ValidationDomainException {

public DuplicateApiMetadataKeyException(String apiId, String key) {
super("Key [" + key + "] already exists for API [" + apiId + "]");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.gravitee.apim.core.api.exception;

import io.gravitee.apim.core.exception.ValidationDomainException;

public class DuplicateApiMetadataNameException extends ValidationDomainException {

public DuplicateApiMetadataNameException(String apiId, String name) {
super("Name [" + name + "] already exists for API [" + apiId + "]");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.gravitee.apim.core.api.exception;

import io.gravitee.apim.core.exception.ValidationDomainException;

public class InvalidApiMetadataValueException extends ValidationDomainException {

public InvalidApiMetadataValueException(String value, String format) {
super("Invalid value [" + value + "] for format [" + format + "]");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,9 @@ public IntegrationCrudServiceInMemory integrationCrudService() {
public IntegrationQueryServiceInMemory integrationQueryService(IntegrationCrudServiceInMemory integrationCrudServiceInMemory) {
return new IntegrationQueryServiceInMemory(integrationCrudServiceInMemory);
}

@Bean
public MetadataCrudServiceInMemory metadataCrudService() {
return new MetadataCrudServiceInMemory();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* Copyright © 2015 The Gravitee team (http://gravitee.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.gravitee.apim.core.api.domain_service;

import static org.assertj.core.api.Assertions.*;

import inmemory.*;
import io.gravitee.apim.core.api.exception.InvalidApiMetadataValueException;
import io.gravitee.apim.core.api.model.Api;
import io.gravitee.apim.core.audit.domain_service.AuditDomainService;
import io.gravitee.apim.core.membership.domain_service.ApiPrimaryOwnerDomainService;
import io.gravitee.apim.core.membership.model.Membership;
import io.gravitee.apim.core.membership.model.Role;
import io.gravitee.apim.core.metadata.model.Metadata;
import io.gravitee.apim.core.user.model.BaseUserEntity;
import io.gravitee.apim.infra.json.jackson.JacksonJsonDiffProcessor;
import io.gravitee.apim.infra.template.FreemarkerTemplateProcessor;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

class ValidateApiMetadataDomainServiceTest {

private static final String API_ID = "my-api";
private static final String ORGANIZATION_ID = "organization-id";
AuditCrudServiceInMemory auditCrudService = new AuditCrudServiceInMemory();
UserCrudServiceInMemory userCrudService = new UserCrudServiceInMemory();
GroupQueryServiceInMemory groupQueryService = new GroupQueryServiceInMemory();
MembershipCrudServiceInMemory membershipCrudService = new MembershipCrudServiceInMemory();
MembershipQueryServiceInMemory membershipQueryService = new MembershipQueryServiceInMemory(membershipCrudService);
RoleQueryServiceInMemory roleQueryService = new RoleQueryServiceInMemory();
MetadataCrudServiceInMemory metadataCrudService = new MetadataCrudServiceInMemory();
ApiMetadataQueryServiceInMemory apiMetadataQueryService;
ValidateApiMetadataDomainService service;

@BeforeEach
void setUp() {
var auditDomainService = new AuditDomainService(auditCrudService, userCrudService, new JacksonJsonDiffProcessor());
var apiMetadataDecoderDomainService = new ApiMetadataDecoderDomainService(
apiMetadataQueryService,
new FreemarkerTemplateProcessor()
);
service =
new ValidateApiMetadataDomainService(
apiMetadataQueryService,
metadataCrudService,
new ApiPrimaryOwnerDomainService(
auditDomainService,
groupQueryService,
membershipCrudService,
membershipQueryService,
roleQueryService,
userCrudService
),
apiMetadataDecoderDomainService
);

roleQueryService.initWith(
List.of(
Role
.builder()
.id("role-id")
.scope(Role.Scope.API)
.referenceType(Role.ReferenceType.ORGANIZATION)
.referenceId(ORGANIZATION_ID)
.name("PRIMARY_OWNER")
.build()
)
);
membershipQueryService.initWith(
List.of(
Membership
.builder()
.id("member-id")
.memberId("my-member-id")
.memberType(Membership.Type.USER)
.referenceType(Membership.ReferenceType.API)
.referenceId(API_ID)
.roleId("role-id")
.build()
)
);
userCrudService.initWith(List.of(BaseUserEntity.builder().id("my-member-id").email("one_valid@email.com").build()));

metadataCrudService.initWith(
List.of(
Metadata.builder().referenceId(API_ID).referenceType(Metadata.ReferenceType.API).key("exists").value("john@doe.com").build()
)
);
}

@AfterEach
void tearDown() {
Stream
.of(
auditCrudService,
membershipCrudService,
roleQueryService,
userCrudService,
metadataCrudService,
groupQueryService,
membershipQueryService
)
.forEach(InMemoryAlternative::reset);
}

@Nested
class ValidateValueByFormat {

@ParameterizedTest
@MethodSource("provideValidParameters")
void should_validate_value(final String value, final Metadata.MetadataFormat format) {
assertThatCode(() -> service.validateValueByFormat(Api.builder().build(), ORGANIZATION_ID, value, format))
.doesNotThrowAnyException();
}

public static Stream<Arguments> provideValidParameters() {
return Stream.of(
Arguments.of("john@doe.com", Metadata.MetadataFormat.MAIL),
Arguments.of("https://my-url.com", Metadata.MetadataFormat.URL),
Arguments.of("https://my-url", Metadata.MetadataFormat.URL),
Arguments.of("2005-01-01", Metadata.MetadataFormat.DATE),
Arguments.of("2021-10-06T15:17:15.282+00:00", Metadata.MetadataFormat.DATE),
Arguments.of("42", Metadata.MetadataFormat.NUMERIC),
Arguments.of("100.5", Metadata.MetadataFormat.NUMERIC),
Arguments.of("true", Metadata.MetadataFormat.BOOLEAN),
Arguments.of("false", Metadata.MetadataFormat.BOOLEAN),
Arguments.of("any value for boolean works", Metadata.MetadataFormat.BOOLEAN),
Arguments.of("100", Metadata.MetadataFormat.STRING)
);
}

@ParameterizedTest
@MethodSource("provideInvalidParameters")
void should_throw_error_on_invalid_value(final String value, final Metadata.MetadataFormat format) {
assertThatThrownBy(() -> service.validateValueByFormat(Api.builder().id(API_ID).build(), ORGANIZATION_ID, value, format))
.isInstanceOf(InvalidApiMetadataValueException.class);
}

public static Stream<Arguments> provideInvalidParameters() {
return Stream.of(
Arguments.of("johndoe.com", Metadata.MetadataFormat.MAIL),
Arguments.of("@johndoe.com", Metadata.MetadataFormat.MAIL),
Arguments.of("my-url", Metadata.MetadataFormat.URL),
Arguments.of("my-url.com", Metadata.MetadataFormat.URL),
Arguments.of("2005", Metadata.MetadataFormat.DATE),
Arguments.of("1/2", Metadata.MetadataFormat.NUMERIC)
);
}
}

@Test
void should_throw_error_when_decoding_mail_value_referencing_another_metadata_key() {
assertThatThrownBy(() ->
service.validateValueByFormat(
Api.builder().id(API_ID).build(),
ORGANIZATION_ID,
"${api.metadata['exists']}",
Metadata.MetadataFormat.MAIL
)
)
.isInstanceOf(InvalidApiMetadataValueException.class);
}

@Test
void should_not_throw_error_when_decoding_string_value_referencing_another_metadata_key() {
assertThatCode(() ->
service.validateValueByFormat(
Api.builder().id(API_ID).build(),
ORGANIZATION_ID,
"${api.metadata['exists']}",
Metadata.MetadataFormat.STRING
)
)
.doesNotThrowAnyException();
}
}

0 comments on commit dbcfcde

Please sign in to comment.