SwaggerOpenApiValidator.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it). 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.openspcoop2.utils.openapi.validator.swagger;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Optional;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.fge.jsonschema.core.exceptions.ProcessingException;
import com.github.fge.jsonschema.core.report.ListProcessingReport;
import com.github.fge.jsonschema.core.report.ListReportProvider;
import com.github.fge.jsonschema.core.report.LogLevel;
import com.github.fge.jsonschema.core.report.ProcessingMessage;
import com.github.fge.jsonschema.core.report.ProcessingReport;
import com.github.fge.jsonschema.main.JsonSchema;
import com.github.fge.jsonschema.main.JsonSchemaFactory;

import io.swagger.util.Json;

/**
 * SwaggerOpenApiValidator
 * 
 * @author $Author$
 * @version $Rev$, $Date$
 *
 */
public class SwaggerOpenApiValidator {

	// vedi io.swagger.handler.ValidatorController
	
	private static final String SCHEMA_OPENAPI3_FILE = "schema-openapi3.json";
	private static final String SCHEMA_SWAGGER2_FILE = "schema-swagger-v2.json";
	private static final String INVALID_VERSION = "Unsupported Swagger version";
	static ObjectMapper JsonMapper = Json.mapper();

	private JsonSchema schemaV2;
	private JsonSchema schemaV3;

	public SwaggerOpenApiValidator() {
		String prefix = "/org/openspcoop2/utils/openapi/validator/swagger/";
		
		/* 
		 * Schemi presi da:
		 * - https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v3.0/schema.json
		 * - https://raw.githubusercontent.com/OAI/OpenAPI-Specification/main/schemas/v2.0/schema.json
		 * 
		 * NOTA: lo schema v3 รจ stato modificato per sostituire tutte le occorrenze di 
		 * 			"format": "uri-reference" 
		 * 		 con 
		 * 			"format": "uri"
		 * 		 Il validatore non supportava uri-reference
		 */		
		this.schemaV2 = resolveJsonSchema(getResourceFileAsString(prefix + SCHEMA_SWAGGER2_FILE), true);
		this.schemaV3 = resolveJsonSchema(getResourceFileAsString(prefix + SCHEMA_OPENAPI3_FILE), true);
	}

	public Optional<String> validate(JsonNode spec) throws ProcessingException {
		
		// Gli swaggerV2 vengono convertiti in OpenAPI, per cui la validazione devo farla prima 
		// di aver convertito lo schema del json node nell'oggetto OpenAPI altrimenti si perdono le info
		
		boolean isVersion2 = false;
		
		String version = SwaggerValidatorUtils.getSchemaVersion(spec);
        if (SwaggerValidatorUtils.isSchemaV1(version)) {
        	return Optional.of(INVALID_VERSION);        	
        } else if (SwaggerValidatorUtils.isSchemaV2(version)) {
            isVersion2 = true;
        } else if (version == null || SwaggerValidatorUtils.isSchemaV3(version)) {
        	// siamo in v3
        }
        
        JsonSchema schema = getSchema(isVersion2);
        ProcessingReport report = schema.validateUnchecked(spec);
        ListProcessingReport lp = new ListProcessingReport();
        lp.mergeWith(report);

        StringBuilder result = new StringBuilder("");
        java.util.Iterator<ProcessingMessage> it = lp.iterator();
        while (it.hasNext()) {
            ProcessingMessage pm = it.next();
            result.append(pm.toString());
            result.append("\n");
        }		

        return result.length() == 0 ? Optional.empty() : Optional.of(result.toString());
	}
	
	
	private JsonSchema getSchema(boolean isVersion2) {
		if (isVersion2) {
			return this.schemaV2;
		} else {
			return this.schemaV3;
		}
	}

	
	private JsonSchema resolveJsonSchema(String schemaAsString, boolean removeId) {
		try {
			JsonNode schemaObject = JsonMapper.readTree(schemaAsString);
			if (removeId) {
				ObjectNode oNode = (ObjectNode) schemaObject;
				if (oNode.get("id") != null) {
					oNode.remove("id");
				}
				if (oNode.get("$schema") != null) {
					oNode.remove("$schema");
				}
				if (oNode.get("description") != null) {
					oNode.remove("description");
				}
			}
			
			// Come schema factory utilizzo quella di atlassian che estende il jsonSchema
			// con altri tipi
			//JsonSchemaFactory factory = SwaggerV20Library.schemaFactory();//JsonSchemaFactory.byDefault();
			JsonSchemaFactory factory = JsonSchemaFactory
					.newBuilder()
			           .setReportProvider(
		                        // Only emit ERROR and above from the JSON schema validation
		                        new ListReportProvider(LogLevel.WARNING, LogLevel.FATAL))
					.freeze();
			
			return factory.getJsonSchema(schemaObject);
		} catch (Exception e) {
			throw new RuntimeException("Errore inatteso durante il parsing JSON dello schema: " + e.getMessage());
		}
	}

	
	public String getResourceFileAsString(String fileName) {
		
		try {
			InputStream is = SwaggerOpenApiValidator.class.getResourceAsStream(fileName);
			if (is != null) {
				BufferedReader reader = new BufferedReader(new InputStreamReader(is));
				var ret = reader.lines().collect(Collectors.joining(System.lineSeparator()));
				reader.close();
				return ret;
			}
		} catch (Exception e) {
			throw new RuntimeException("Errore inatteso durante la lettura del file dello schema: " + e.getMessage());
		}
		return null;
	}

}