스웨거(Swagger) - @ExampleObject value 속 긴 String 분리 과정 정리

2024. 3. 15. 00:01자바/자바스프링

728x90
반응형

#0. 개요

  • controller에서 put / post method API 를 swagger 에 작성할 때, 스키마 뿐만 아니라, ExampleObject 코드로도 request body 가 어떤 모양이어야 하는 지 프론트엔드에게 설명을 해주고 싶다.
    • 원할한 프론트 - 백 소통을 위해
  • 그러나, controller의 코드가 swagger example value 문자열 때문에 너무 길어져서 백엔드 코드 가독성이 떨어졌다
    • 심지어 exampleObject가 controller 마다 여러개 일 수도 있고, request Body Dto Class가 property 가 많으면 종잡을 수가 없다.
    @PostMapping("/item")
    @Operation(description = "", summary = "메뉴 등록 API")
    public ResponseEntity<JsonResponse> operateFund(
        HttpServletRequest request,
        @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(
            examples = {
                @ExampleObject(name = "메뉴 등록", value = """
                        {
                            "id": 1,
                        //.. 중략
                        }
                    """, description = "설명")
            }))
        @org.springframework.web.bind.annotation.RequestBody MenuDto requestBody) {
    
        //..중략
        JsonResponse response = JsonResponse.builder()
            .success(true)
            .code(ResultMessageCode.API_SUCCESS.getReturnCode())
            .message(ResultMessageCode.API_SUCCESS.getReturnMessage())
            .build();
    
        return ResponseEntity.ok(response);
    }
  • 어딘가에서 이 긴 json string 값들을 한 곳에서 보관하고, swagger 생성 시점에 주입시킬 수 없을까?
  • 따라서 @ExampleObject 어노테이션의 externalValue 나 ref 를 활용할 방법을 검색
    • 그런데 externalValue를 사용하지 못한다는 글이 많았고 실제로 그랬음
    • 깃헙 이슈 댓글과 스택오버플로우를 찾던 중, 간간히 사람들이 올려준 다른 방법을 적용시켜보았다.
  • 참조:

#1. 구현

1. 초기세팅

  • 빌드 그래들:
  • implementation 'com.googlecode.json-simple:json-simple:1.1.1'
  • json 파일 생성:
    • 각 post, put request body에 썼던 json text 들을 단일 json 파일에 저장.
      • 파일 위치는 resources/static/swagger 이다.
      • 디렉토리가 없으면 새로 만들거나, 편한 곳에 둬도 상관 없다.
    • key 값 명칭은 url (사이에는 / 가 아닌 . 으로 처리)
      • 이유는 추후 controller 에서 example Object 로 참조 할 때 아래 경로로 참조하기 때문
        • 이때 key 명칭을 / 로 했으면 / 를 디렉토리 하위 경로로 인식
      @ExampleObject(name = "메뉴 수정 예시 1", ref = "#/components/examples/menu.put")
      
  • swaggerExampleObject구현
    • 메서드 역할:
      • json 파일 → Example 객체 리스트로 변환
      • 각 example의 description에 key 값을 매핑해서 추후 swagger 에 example Object를 주입시킬 때 description 으로 찾아감.
import io.swagger.v3.oas.models.examples.Example;

@Component
public class SwaggerExampleObject {

	@Value("classpath:/static/swagger/requestBody.json")
	Resource resource;

	@Bean
	public List<Example> exampleJsonValueMaker() throws
		IOException,
		ParseException,
		IllegalAccessError {

		JSONObject json = (JSONObject)new JSONParser().parse(
			new InputStreamReader(resource.getInputStream(), "UTF-8"));

		List<Example> exampleList = new ArrayList<>();

		for (Object s : json.keySet()) {
			Example example = new Example();
			example.setValue(json.get(s));
			example.setDescription(s.toString());
			exampleList.add(example);
		}

		return exampleList;
	}

}
  • example 리스트 swagger 주입
    • SwaggerConfig 측:
    • for (Example e : swaggerExampleList) { openAPI.getComponents().addExamples(e.getDescription(), e); } return openAPI;

 

2. 여러 파일 대응

  • 기존에는 Resource 객체에 단일 파일 경로만 집어넣어서 가져감.
    • 이때 각 controller의 모듈에 따라 JSON string을 각각 다른 파일에 넣고 싶다는 니즈가 생김.
      • 따라서 단일 json 파일이 아닌 여러 json 파일로 example 객체 리스트를 만들어 주입시키도록 수정.
      • listFiles() :
        • 디렉토리의 파일목록을 파일 배열로 반환한다
@Component
public class SwaggerExampleObject {

	@Value("classpath:/static/swagger")
	File file;

	@Bean
	public List<Example> exampleJsonValueMaker() throws
		IOException,
		ParseException,
		IllegalAccessError {
		List<Example> exampleList = new ArrayList<>();

		File[] swaggerExampleJsonFiles = file.listFiles();
		for (File file : swaggerExampleJsonFiles) {
			Resource resource = new FileSystemResource(file);
			JSONObject json = (JSONObject)new JSONParser().parse(
				new InputStreamReader(resource.getInputStream(), "UTF-8"));

			for (Object s : json.keySet()) {
				Example example = new Example();
				example.setValue(json.get(s));
				example.setDescription(s.toString());
				exampleList.add(example);
			}
		}

		return exampleList;
	}

}

 

  • 그래서 File 객체로 디렉토리 전체를 가져와서 파일 메서드 listFiles 로 파일 객체로 수정해줌. 그러나 배포 후 develop 서버로 올라갔을 때 해당 경로가 인식이 안 되고 오류가 난다!
  • 이유인 즉슨,
    • File은 파일 시스템의 로컬 파일을 기반을 둠
    • Resource 객체는 URL 기반으로 파일을 찾음
  • 수정 코드:
    • PathMatchingResourcePatternResolver:
      • classPath 상의 정적 리소스를 찾아서, 각 리소스에 대해 JSON 파일을 읽어와 JSONObject 를 파싱한다.
      • getResources() 메서드로 정규식 문자열을 넣어주면, 해당 정규식 패턴과 일치하는 리소스의 정보를 가져올 수 있다.
          • getResource() 메서드랑 다름. 해당 메서드는 정규식 지원 X
@Component
public class SwaggerExampleObject {

	@Value("classpath:/static/swagger")
	File file;

	@Bean
	public List<Example> exampleJsonValueMaker() throws
		IOException,
		ParseException,
		IllegalAccessError {
		List<Example> exampleList = new ArrayList<>();

		File[] swaggerExampleJsonFiles = file.listFiles();
		for (File file : swaggerExampleJsonFiles) {
			Resource resource = new FileSystemResource(file);
			JSONObject json = (JSONObject)new JSONParser().parse(
				new InputStreamReader(resource.getInputStream(), "UTF-8"));

			for (Object s : json.keySet()) {
				Example example = new Example();
				example.setValue(json.get(s));
				example.setDescription(s.toString());
				exampleList.add(example);
			}
		}

		return exampleList;
	}

}

#2. 사용 정리

1. controller 단

  • post, put API request body exampleObject 단순화
@RequestBody(content = @Content(
		examples = {
			@ExampleObject(name = "메뉴 수정 예시 1", ref = "#/components/examples/menu.put"))
		})
)

2. requestBody.json

  • resources.static.swagger 폴더 내에 위치함
  • 기존 작업된 내용을 requestBody.json 내의 json value 값으로 추가
    • key값 부여 방식은 겹치지 않는 방식으로
    • 본인은 url + api method 방식으로 결정
      • 중간을 / 가 아닌 . 으로
{
  "menu.put": [
    {
      "id": 1,
      "name": "검색"
    }
  ],
  "menu.post": [
    {
      "name": "검색"
    }
  ],
}

 

728x90
반응형