Skip to content

Commit 6285fc1

Browse files
committed
feat: add OpenAPI configuration, exception handler, and conditions report support
1 parent b8e3e6a commit 6285fc1

6 files changed

Lines changed: 588 additions & 0 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2024-2026 Firefly Software Solutions Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.fireflyframework.web.conditions.config;
18+
19+
import org.springframework.boot.autoconfigure.AutoConfiguration;
20+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
21+
22+
/**
23+
* Base auto-configuration that registers ConditionsReportProperties.
24+
*
25+
* <p>This configuration is always loaded (unconditionally) to ensure that
26+
* the {@link ConditionsReportProperties} bean is available in the application context,
27+
* even when the conditions report feature is disabled.</p>
28+
*
29+
* <p>This allows the properties to be bound from configuration files without
30+
* requiring the feature to be enabled, which is necessary for proper Spring Boot
31+
* configuration property binding.</p>
32+
*
33+
* <p>The actual conditions report functionality is only activated when
34+
* {@code firefly.conditions-report.enabled=true} via
35+
* {@link ConditionsReportAutoConfiguration}.</p>
36+
*/
37+
@AutoConfiguration
38+
@EnableConfigurationProperties(ConditionsReportProperties.class)
39+
public class ConditionsReportPropertiesConfiguration {
40+
// This class intentionally has no beans
41+
// It only exists to register ConditionsReportProperties unconditionally
42+
}
43+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2024-2026 Firefly Software Solutions Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
18+
package org.fireflyframework.web.configuration;
19+
20+
import org.springframework.boot.autoconfigure.AutoConfiguration;
21+
import org.springframework.context.annotation.ComponentScan;
22+
import org.springframework.context.annotation.Configuration;
23+
24+
25+
/**
26+
* Configuration class for exception handling.
27+
* This class provides Spring auto-configuration for the global exception handler
28+
* and exception conversion used throughout the application.
29+
*/
30+
@Configuration
31+
@AutoConfiguration
32+
@ComponentScan(basePackages = {
33+
"org.fireflyframework.web.error.handler",
34+
"org.fireflyframework.web.error.converter",
35+
"org.fireflyframework.web.error.config",
36+
"org.fireflyframework.web.error.service"
37+
})
38+
public class ExceptionHandlerConfiguration {
39+
40+
/**
41+
* Creates a new ExceptionHandlerConfiguration.
42+
*/
43+
public ExceptionHandlerConfiguration() {
44+
// Default constructor
45+
}
46+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2024-2026 Firefly Software Solutions Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
18+
package org.fireflyframework.web.error.examples;
19+
20+
import org.fireflyframework.web.error.exceptions.BusinessException;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
22+
import org.springframework.dao.DataIntegrityViolationException;
23+
import org.springframework.dao.OptimisticLockingFailureException;
24+
import org.springframework.web.bind.annotation.GetMapping;
25+
import org.springframework.web.bind.annotation.PathVariable;
26+
import org.springframework.web.bind.annotation.RequestMapping;
27+
import org.springframework.web.bind.annotation.RestController;
28+
import reactor.core.publisher.Mono;
29+
30+
import java.util.concurrent.TimeoutException;
31+
32+
/**
33+
* Example controller that demonstrates how to use the exception handling features.
34+
* This controller shows how to throw business exceptions directly and how to use
35+
* the exception conversion mechanism in a REST controller.
36+
*/
37+
@RestController
38+
@RequestMapping("/api/examples/exceptions")
39+
@ConditionalOnClass({DataIntegrityViolationException.class, OptimisticLockingFailureException.class})
40+
public class ExceptionHandlingController {
41+
42+
private final ExceptionHandlingExample exampleService;
43+
44+
/**
45+
* Creates a new ExceptionHandlingController with the given example service.
46+
*
47+
* @param exampleService the example service
48+
*/
49+
public ExceptionHandlingController(ExceptionHandlingExample exampleService) {
50+
this.exampleService = exampleService;
51+
}
52+
53+
/**
54+
* Example endpoint that throws business exceptions directly.
55+
*
56+
* @param type the type of exception to throw
57+
* @return a Mono that completes with an error
58+
*/
59+
@GetMapping("/business/{type}")
60+
public Mono<String> throwBusinessException(@PathVariable String type) {
61+
return Mono.defer(() -> {
62+
exampleService.throwBusinessException(type);
63+
return Mono.just("This should never be returned");
64+
});
65+
}
66+
67+
/**
68+
* Example endpoint that manually converts standard exceptions to business exceptions.
69+
*
70+
* @param type the type of exception to convert
71+
* @return a Mono that completes with an error
72+
*/
73+
@GetMapping("/convert/{type}")
74+
public Mono<String> convertStandardException(@PathVariable String type) {
75+
return Mono.defer(() -> {
76+
BusinessException exception = exampleService.convertStandardException(type);
77+
return Mono.error(exception);
78+
});
79+
}
80+
81+
/**
82+
* Example endpoint that automatically converts standard exceptions to business exceptions.
83+
* The GlobalExceptionHandler will automatically convert any exceptions to business exceptions.
84+
*
85+
* @param type the type of exception to throw
86+
* @return a Mono that completes with an error
87+
*/
88+
@GetMapping("/auto-convert/{type}")
89+
public Mono<String> throwStandardException(@PathVariable String type) {
90+
return Mono.defer(() -> {
91+
try {
92+
exampleService.throwStandardException(type);
93+
return Mono.just("This should never be returned");
94+
} catch (TimeoutException e) {
95+
// The GlobalExceptionHandler will automatically convert this to a BusinessException
96+
throw new RuntimeException("Timeout occurred", e);
97+
}
98+
});
99+
}
100+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* Copyright 2024-2026 Firefly Software Solutions Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
18+
package org.fireflyframework.web.error.examples;
19+
20+
import org.fireflyframework.web.error.converter.ExceptionConverterService;
21+
import org.fireflyframework.web.error.exceptions.*;
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
23+
import org.springframework.dao.DataIntegrityViolationException;
24+
import org.springframework.dao.OptimisticLockingFailureException;
25+
import org.springframework.stereotype.Service;
26+
import org.springframework.web.client.HttpClientErrorException;
27+
import org.springframework.web.client.HttpServerErrorException;
28+
29+
import java.util.concurrent.TimeoutException;
30+
31+
/**
32+
* Example service that demonstrates how to use the exception handling features.
33+
* This class shows how to throw business exceptions directly and how to use
34+
* the exception conversion mechanism.
35+
*/
36+
@Service
37+
@ConditionalOnClass({DataIntegrityViolationException.class, OptimisticLockingFailureException.class})
38+
public class ExceptionHandlingExample {
39+
40+
private final ExceptionConverterService converterService;
41+
42+
/**
43+
* Creates a new ExceptionHandlingExample with the given converter service.
44+
*
45+
* @param converterService the exception converter service
46+
*/
47+
public ExceptionHandlingExample(ExceptionConverterService converterService) {
48+
this.converterService = converterService;
49+
}
50+
51+
/**
52+
* Example of throwing business exceptions directly.
53+
* This method demonstrates how to create and throw various business exceptions.
54+
*
55+
* @param exceptionType the type of exception to throw
56+
* @throws BusinessException the business exception
57+
*/
58+
public void throwBusinessException(String exceptionType) throws BusinessException {
59+
switch (exceptionType) {
60+
case "resource-not-found":
61+
throw ResourceNotFoundException.forResource("User", "123");
62+
case "invalid-request":
63+
throw InvalidRequestException.forField("email", "invalid-email", "must be a valid email format");
64+
case "conflict":
65+
throw ConflictException.resourceAlreadyExists("User", "john.doe@example.com");
66+
case "unauthorized":
67+
throw UnauthorizedException.missingAuthentication();
68+
case "forbidden":
69+
throw ForbiddenException.insufficientPermissions("ADMIN");
70+
case "service-error":
71+
throw ServiceException.withCause("Failed to process request", new RuntimeException("Database connection failed"));
72+
case "validation-error":
73+
ValidationException.Builder validationBuilder = new ValidationException.Builder()
74+
.addError("email", "must be a valid email")
75+
.addError("password", "must be at least 8 characters");
76+
throw validationBuilder.build();
77+
case "third-party-service":
78+
throw ThirdPartyServiceException.serviceUnavailable("PaymentService");
79+
case "rate-limit":
80+
throw RateLimitException.forUser("user123", 60);
81+
case "data-integrity":
82+
throw DataIntegrityException.uniqueConstraintViolation("email", "john.doe@example.com");
83+
case "operation-timeout":
84+
throw OperationTimeoutException.databaseTimeout("query", 5000);
85+
case "concurrency":
86+
throw ConcurrencyException.optimisticLockingFailure("User", "123");
87+
case "authorization":
88+
throw AuthorizationException.missingPermission("USER_WRITE");
89+
default:
90+
throw new BusinessException("Unknown exception type: " + exceptionType);
91+
}
92+
}
93+
94+
/**
95+
* Example of manually converting standard exceptions to business exceptions.
96+
* This method demonstrates how to use the converter service to convert exceptions.
97+
*
98+
* @param exceptionType the type of exception to convert
99+
* @return the converted business exception
100+
*/
101+
public BusinessException convertStandardException(String exceptionType) {
102+
Throwable exception;
103+
104+
switch (exceptionType) {
105+
case "data-integrity":
106+
exception = new DataIntegrityViolationException("Duplicate entry 'john.doe@example.com' for key 'email'");
107+
break;
108+
case "optimistic-locking":
109+
exception = new OptimisticLockingFailureException("Failed to update entity User with id '123' - it was modified by another transaction");
110+
break;
111+
case "client-error":
112+
exception = HttpClientErrorException.NotFound.create(org.springframework.http.HttpStatus.NOT_FOUND, "Not Found", null, null, null);
113+
break;
114+
case "server-error":
115+
exception = HttpServerErrorException.ServiceUnavailable.create(org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE, "Service Unavailable", null, null, null);
116+
break;
117+
case "timeout":
118+
exception = new TimeoutException("Operation timed out after 5000ms");
119+
break;
120+
default:
121+
exception = new RuntimeException("Unknown exception: " + exceptionType);
122+
break;
123+
}
124+
125+
return converterService.convertException(exception);
126+
}
127+
128+
/**
129+
* Example of automatically converting standard exceptions to business exceptions.
130+
* Any exceptions thrown by this method will be automatically converted to business exceptions
131+
* by the GlobalExceptionHandler.
132+
*
133+
* @param exceptionType the type of exception to throw
134+
* @throws BusinessException the converted business exception
135+
* @throws TimeoutException if a timeout occurs
136+
*/
137+
public void throwStandardException(String exceptionType) throws BusinessException, TimeoutException {
138+
switch (exceptionType) {
139+
case "data-integrity":
140+
throw new DataIntegrityViolationException("Duplicate entry 'john.doe@example.com' for key 'email'");
141+
case "optimistic-locking":
142+
throw new OptimisticLockingFailureException("Failed to update entity User with id '123' - it was modified by another transaction");
143+
case "client-error":
144+
throw HttpClientErrorException.NotFound.create(org.springframework.http.HttpStatus.NOT_FOUND, "Not Found", null, null, null);
145+
case "server-error":
146+
throw HttpServerErrorException.ServiceUnavailable.create(org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE, "Service Unavailable", null, null, null);
147+
case "timeout":
148+
throw new TimeoutException("Operation timed out after 5000ms");
149+
default:
150+
throw new RuntimeException("Unknown exception: " + exceptionType);
151+
}
152+
}
153+
}

0 commit comments

Comments
 (0)