001/* 002 * SPDX-License-Identifier: Apache-2.0 003 * 004 * Copyright 2023-2026 The Enola <https://enola.dev> Authors 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package dev.enola.common.protobuf; 019 020import static java.util.Collections.emptySet; 021 022import com.google.common.collect.ImmutableMap; 023import com.google.common.collect.ImmutableMultimap; 024import com.google.common.collect.ImmutableSet; 025import com.google.common.collect.Multimap; 026import com.google.common.net.MediaType; 027 028import dev.enola.common.io.mediatype.MediaTypeProvider; 029import dev.enola.common.io.mediatype.MediaTypes; 030 031import java.nio.charset.StandardCharsets; 032import java.util.Map; 033import java.util.Optional; 034import java.util.Set; 035 036public class ProtobufMediaTypes implements MediaTypeProvider { 037 // TODO move this class into the common.proto module! 038 039 // The *.proto ("schemas") files, as well as "Text Proto" (and their JSON and YAML equivalent) 040 // are text/* and not application/* whereas the binary serialization wire format is an 041 // "application/*"; 042 // based on https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types. 043 044 /** 045 * MediaType parameter to indicate the resource's root message fully qualified name. Inspired by 046 * https://protobuf.dev/reference/protobuf/textformat-spec/#header. 047 */ 048 public static final String PARAMETER_PROTO_MESSAGE = "proto-message"; 049 050 public static MediaType setProtoMessageFQN(MediaType mediaType, String protoFQN) { 051 return mediaType.withParameter(PARAMETER_PROTO_MESSAGE, protoFQN); 052 } 053 054 // TODO After move: public static MediaType setProtoMessage(MediaType mediaType, Descriptor 055 // descriptor) { 056 // return mediaType.withParameter(PARAMETER_PROTO_MESSAGE, descriptor.getFullName()); 057 // } 058 059 public static Optional<String> getProtoMessageFQN(MediaType mediaType) { 060 return MediaTypes.parameter(mediaType, PARAMETER_PROTO_MESSAGE); 061 } 062 063 // TODO Support "sniffing" proto-message from header in comment of ReadableResource 064 065 public static final MediaType PROTO_UTF_8 = 066 MediaType.create("text", "proto").withCharset(StandardCharsets.UTF_8); 067 068 public static final MediaType PROTOBUF_TEXTPROTO_UTF_8 = 069 MediaType.create("text", "protobuf").withCharset(StandardCharsets.UTF_8); 070 071 /** "ProtoBuf as JSON" - which is different from e.g. a JSON-LD representation of RDF. */ 072 public static final MediaType PROTOBUF_JSON_UTF_8 = 073 MediaType.create("text", "protobuf+json").withCharset(StandardCharsets.UTF_8); 074 075 /** "ProtoBuf as YAML" - which is different from e.g. a YAML-LD representation of RDF. */ 076 public static final MediaType PROTOBUF_YAML_UTF_8 = 077 MediaType.create("text", "protobuf+yaml").withCharset(StandardCharsets.UTF_8); 078 079 // https://datatracker.ietf.org/doc/html/draft-rfernando-protocol-buffers-00 080 public static final MediaType PROTOBUF_BINARY = MediaType.create("application", "protobuf"); 081 082 @Override 083 public Map<MediaType, Set<MediaType>> knownTypesWithAlternatives() { 084 return ImmutableMap.of( 085 PROTO_UTF_8, 086 emptySet(), 087 PROTOBUF_TEXTPROTO_UTF_8, 088 emptySet(), 089 PROTOBUF_JSON_UTF_8, 090 emptySet(), 091 PROTOBUF_YAML_UTF_8, 092 emptySet(), 093 PROTOBUF_BINARY, 094 // https://stackoverflow.com/questions/30505408/what-is-the-correct-protobuf-content-type 095 ImmutableSet.of( 096 MediaType.create("application", "x-protobuf"), 097 MediaType.create("application", "vnd.google.protobuf"))); 098 } 099 100 @Override 101 public Multimap<String, MediaType> extensionsToTypes() { 102 // https://protobuf.dev/programming-guides/techniques/#suffixes 103 return ImmutableMultimap.<String, MediaType>builder() 104 .put(".proto", ProtobufMediaTypes.PROTO_UTF_8) 105 .put( 106 ".proto.binpb", 107 // TODO This parameter isn't actually used for anything... yet. 108 ProtobufMediaTypes.PROTOBUF_BINARY.withParameter( 109 PARAMETER_PROTO_MESSAGE, "google.protobuf.FileDescriptorSet")) 110 .put(".binpb", ProtobufMediaTypes.PROTOBUF_BINARY) 111 .put(".pb", ProtobufMediaTypes.PROTOBUF_BINARY) 112 .put(".textproto", ProtobufMediaTypes.PROTOBUF_TEXTPROTO_UTF_8) 113 .put(".txtpb", ProtobufMediaTypes.PROTOBUF_TEXTPROTO_UTF_8) 114 .build(); 115 } 116}