Skip to content

Commit

Permalink
Merge branch 'specification-string-properties-customizer' of https://…
Browse files Browse the repository at this point in the history
…github.com/tkachenkoas/springdoc-openapi into tkachenkoas-specification-string-properties-customizer
  • Loading branch information
bnasslahsen committed Feb 26, 2024
2 parents 4673b6a + 4618041 commit 69d1649
Show file tree
Hide file tree
Showing 10 changed files with 536 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
*
* *
* * *
* * * *
* * * * * Copyright 2019-2022 the original author or authors.
* * * * *
* * * * * 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
* * * * *
* * * * * https://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 org.springdoc.core.configuration;

import org.springdoc.core.customizers.SpecificationStringPropertiesCustomizer;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.PropertyResolver;

import java.util.List;

/**
* The type Spring doc specification string properties configuration.
*
* @author Anton Tkachenko tkachenkoas@gmail.com
*/
@Lazy(false)
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "springdoc.api-docs.specification-string-properties")
@ConditionalOnBean(SpringDocConfiguration.class)
public class SpringDocSpecificationStringPropertiesConfiguration {

/**
* Springdoc customizer that takes care of the specification string properties customization.
* Will be applied to general openapi schema.
*
* @return the springdoc customizer
*/
@Bean
@ConditionalOnMissingBean
@Lazy(false)
SpecificationStringPropertiesCustomizer specificationStringPropertiesCustomizer(
PropertyResolver propertyResolverUtils
) {
return new SpecificationStringPropertiesCustomizer(propertyResolverUtils);
}

/**
* Bean post processor that applies the specification string properties customization to
* grouped openapi schemas by using group name as a prefix for properties.
*
* @return the bean post processor
*/
@Bean
@ConditionalOnMissingBean
@Lazy(false)
SpecificationStringPropertiesCustomizerBeanPostProcessor specificationStringPropertiesCustomizerBeanPostProcessor(
PropertyResolver propertyResolverUtils
) {
return new SpecificationStringPropertiesCustomizerBeanPostProcessor(propertyResolverUtils);
}


private static class SpecificationStringPropertiesCustomizerBeanPostProcessor implements BeanPostProcessor {

private final PropertyResolver propertyResolverUtils;

public SpecificationStringPropertiesCustomizerBeanPostProcessor(
PropertyResolver propertyResolverUtils
) {
this.propertyResolverUtils = propertyResolverUtils;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof GroupedOpenApi groupedOpenApi) {
groupedOpenApi.addAllOpenApiCustomizer(List.of(new SpecificationStringPropertiesCustomizer(
propertyResolverUtils, groupedOpenApi.getGroup()
)));
}
return bean;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
*
* *
* * *
* * * *
* * * * * Copyright 2019-2022 the original author or authors.
* * * * *
* * * * * 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
* * * * *
* * * * * https://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 org.springdoc.core.customizers;

import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.media.Schema;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.PropertyResolver;
import org.springframework.util.CollectionUtils;

import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

/**
* Allows externalizing strings in generated openapi schema via properties that follow
* conventional naming similar or identical to <a href="https://swagger.io/docs/specification/basic-structure/">openapi schema</a>
* <p>
* To set value of a string in schema, define an application property that matches the target node
* with springdoc.specification-strings prefix.
* <p>
* Sample supported properties for api-info customization:
* <ul>
* <li>springdoc.specification-strings.info.title - to set title of api-info</li>
* <li>springdoc.specification-strings.info.description - to set description of api-info</li>
* <li>springdoc.specification-strings.info.version - to set version of api-info</li>
* <li>springdoc.specification-strings.info.termsOfService - to set terms of service of api-info</li>
* </ul>
* <p>
* Sample supported properties for components customization:
* <ul>
* <li>springdoc.specification-strings.components.User.description - to set description of User model</li>
* <li>springdoc.specification-strings.components.User.properties.name.description - to set description of 'name' property</li>
* <li>springdoc.specification-strings.components.User.properties.name.example - to set example of 'name' property</li>
* </ul>
* <p>
* Sample supported properties for paths/operationIds customization:
* <ul>
* <li>springdoc.specification-strings.paths.{operationId}.description - to set description of {operationId}</li>
* <li>springdoc.specification-strings.paths.{operationId}.summary - to set summary of {operationId}</li>
* </ul>
* <p>
* Support for groped openapi customization is similar to the above, but with a group name prefix.
* E.g.
* <ul>
* <li>springdoc.specification-strings.{group-name}.info.title - to set title of api-info</li>
* <li>springdoc.specification-strings.{group-name}.components.User.description - to set description of User model</li>
* <li>springdoc.specification-strings.{group-name}.paths.{operationId}.description - to set description of {operationId}</li>
* </ul>
*
* @author Anton Tkachenko tkachenkoas@gmail.com
*/
public class SpecificationStringPropertiesCustomizer implements GlobalOpenApiCustomizer {

private static final String SPECIFICATION_STRINGS_PREFIX = "springdoc.specification-strings.";

private final PropertyResolver propertyResolver;
private final String propertyPrefix;

public SpecificationStringPropertiesCustomizer(PropertyResolver resolverUtils) {
this.propertyResolver = resolverUtils;
this.propertyPrefix = SPECIFICATION_STRINGS_PREFIX;
}

public SpecificationStringPropertiesCustomizer(PropertyResolver propertyResolver, String groupName) {
this.propertyResolver = propertyResolver;
this.propertyPrefix = SPECIFICATION_STRINGS_PREFIX + groupName + ".";
}

@Override
public void customise(OpenAPI openApi) {
setOperationInfoProperties(openApi);
setComponentsProperties(openApi);
setPathsProperties(openApi);
}

private void setOperationInfoProperties(OpenAPI openApi) {
if (openApi.getInfo() == null) {
openApi.setInfo(new Info());
}
Info info = openApi.getInfo();
resolveString(info::setTitle, "info.title");
resolveString(info::setDescription, "info.description");
resolveString(info::setVersion, "info.version");
resolveString(info::setTermsOfService, "info.termsOfService");
}

private void setPathsProperties(OpenAPI openApi) {
Paths paths = openApi.getPaths();
if (CollectionUtils.isEmpty(paths.values())) {
return;
}
for (PathItem pathItem : paths.values()) {
List<Operation> operations = pathItem.readOperations();
for (Operation operation : operations) {
String operationId = operation.getOperationId();
String operationNode = MessageFormat.format("paths.{0}", operationId);
resolveString(operation::setDescription, operationNode + ".description");

resolveString(operation::setSummary, operationNode + ".summary");
}
}
}

private void setComponentsProperties(OpenAPI openApi) {
Components components = openApi.getComponents();
if (components == null || CollectionUtils.isEmpty(components.getSchemas())) {
return;
}

for (Schema componentSchema : components.getSchemas().values()) {
// set component description
String schemaPropertyPrefix = MessageFormat.format("components.schemas.{0}", componentSchema.getName());
resolveString(componentSchema::setDescription, schemaPropertyPrefix + ".description");
Map<String, Schema> properties = componentSchema.getProperties();

if (CollectionUtils.isEmpty(properties)) {
continue;
}

for (Schema propSchema : properties.values()) {
String propertyNode = MessageFormat.format("components.schemas.{0}.properties.{1}",
componentSchema.getName(), propSchema.getName());

resolveString(propSchema::setDescription, propertyNode + ".description");
resolveString(propSchema::setExample, propertyNode + ".example");
}
}
}

private void resolveString(
Consumer<String> setter, String node
) {
String nodeWithPrefix = propertyPrefix + node;
String value = propertyResolver.getProperty(nodeWithPrefix);
if (StringUtils.isNotBlank(value)) {
setter.accept(value);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ public final class Constants {
*/
public static final String SPRINGDOC_SCHEMA_RESOLVE_PROPERTIES = "springdoc.api-docs.resolve-schema-properties";

/**
* The constant SPRINGDOC_SPECIFICATION_STRING_PROPERTIES.
*/
public static final String SPRINGDOC_SPECIFICATION_STRING_PROPERTIES = "springdoc.api-docs.specification-string-properties";

/**
* The constant SPRINGDOC_SHOW_LOGIN_ENDPOINT.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ org.springdoc.core.configuration.SpringDocFunctionCatalogConfiguration
org.springdoc.core.configuration.SpringDocHateoasConfiguration
org.springdoc.core.configuration.SpringDocPageableConfiguration
org.springdoc.core.configuration.SpringDocSortConfiguration
org.springdoc.core.configuration.SpringDocSpecificationStringPropertiesConfiguration
org.springdoc.core.configuration.SpringDocDataRestConfiguration
org.springdoc.core.configuration.SpringDocKotlinConfiguration
org.springdoc.core.configuration.SpringDocKotlinxConfiguration
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
*
* * Copyright 2019-2023 the original author or authors.
* *
* * 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
* *
* * https://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 test.org.springdoc.api.app212;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

@GetMapping(value = "/persons")
public PersonDTO persons() {
return new PersonDTO("John");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
*
* * Copyright 2019-2023 the original author or authors.
* *
* * 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
* *
* * https://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 test.org.springdoc.api.app212;

public record PersonDTO(String name) {
}
Loading

0 comments on commit 69d1649

Please sign in to comment.