Skip to content

Commit

Permalink
Merge pull request #1461 from microsoft/feature/vnd-types
Browse files Browse the repository at this point in the history
adds support for vendor specific content types
  • Loading branch information
baywet authored Mar 30, 2022
2 parents 7f44136 + a25c03c commit 438918a
Show file tree
Hide file tree
Showing 13 changed files with 327 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added support for vendor specific content types generation/serialization. [#1197](https://github.com/microsoft/kiota/issues/1197)
- Added support for 204 no content in generation and CSharp/Java/Go/TypeScript request adapters. #1410
- Added a draft swift generation implementation. #1444

Expand Down
12 changes: 12 additions & 0 deletions abstractions/go/api_client_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ func (*mockSerializer) WriteStringValue(key string, value *string) error {
func (*mockSerializer) WriteBoolValue(key string, value *bool) error {
return nil
}
func (*mockSerializer) WriteByteValue(key string, value *byte) error {
return nil
}
func (*mockSerializer) WriteInt8Value(key string, value *int8) error {
return nil
}
func (*mockSerializer) WriteInt32Value(key string, value *int32) error {
return nil
}
Expand Down Expand Up @@ -59,6 +65,12 @@ func (*mockSerializer) WriteCollectionOfStringValues(key string, collection []st
func (*mockSerializer) WriteCollectionOfBoolValues(key string, collection []bool) error {
return nil
}
func (*mockSerializer) WriteCollectionOfByteValues(key string, collection []byte) error {
return nil
}
func (*mockSerializer) WriteCollectionOfInt8Values(key string, collection []int8) error {
return nil
}
func (*mockSerializer) WriteCollectionOfInt32Values(key string, collection []int32) error {
return nil
}
Expand Down
23 changes: 17 additions & 6 deletions abstractions/go/serialization/parse_node_factory_registry.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package serialization

import "errors"
import (
"errors"
re "regexp"
"strings"
)

// ParseNodeFactoryRegistry holds a list of all the registered factories for the various types of nodes.
type ParseNodeFactoryRegistry struct {
Expand All @@ -17,6 +21,8 @@ func (m *ParseNodeFactoryRegistry) GetValidContentType() (string, error) {
return "", errors.New("the registry supports multiple content types. Get the registered factory instead")
}

var contentTypeVendorCleanupPattern = re.MustCompile("[^/]+\\+")

// GetRootParseNode returns a new ParseNode instance that is the root of the content
func (m *ParseNodeFactoryRegistry) GetRootParseNode(contentType string, content []byte) (ParseNode, error) {
if contentType == "" {
Expand All @@ -25,10 +31,15 @@ func (m *ParseNodeFactoryRegistry) GetRootParseNode(contentType string, content
if content == nil {
return nil, errors.New("content is required")
}
factory, ok := m.ContentTypeAssociatedFactories[contentType]
if !ok {
return nil, errors.New("content type " + contentType + " does not have a factory registered to be parsed")
} else {
return factory.GetRootParseNode(contentType, content)
vendorSpecificContentType := strings.Split(contentType, ";")[0]
factory, ok := m.ContentTypeAssociatedFactories[vendorSpecificContentType]
if ok {
return factory.GetRootParseNode(vendorSpecificContentType, content)
}
cleanedContentType := contentTypeVendorCleanupPattern.ReplaceAllString(vendorSpecificContentType, "")
factory, ok = m.ContentTypeAssociatedFactories[cleanedContentType]
if ok {
return factory.GetRootParseNode(cleanedContentType, content)
}
return nil, errors.New("content type " + cleanedContentType + " does not have a factory registered to be parsed")
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package serialization

import "errors"
import (
"errors"
"strings"
)

// SerializationWriterFactoryRegistry is a factory holds a list of all the registered factories for the various types of nodes.
type SerializationWriterFactoryRegistry struct {
Expand All @@ -23,10 +26,15 @@ func (m *SerializationWriterFactoryRegistry) GetSerializationWriter(contentType
if contentType == "" {
return nil, errors.New("the content type is empty")
}
factory := m.ContentTypeAssociatedFactories[contentType]
if factory == nil {
return nil, errors.New("Content type " + contentType + " does not have a factory registered to be parsed")
} else {
vendorSpecificContentType := strings.Split(contentType, ";")[0]
factory, ok := m.ContentTypeAssociatedFactories[vendorSpecificContentType]
if ok {
return factory.GetSerializationWriter(contentType)
}
cleanedContentType := contentTypeVendorCleanupPattern.ReplaceAllString(vendorSpecificContentType, "")
factory, ok = m.ContentTypeAssociatedFactories[cleanedContentType]
if ok {
return factory.GetSerializationWriter(cleanedContentType)
}
return nil, errors.New("Content type " + cleanedContentType + " does not have a factory registered to be parsed")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package serialization

import (
assert "github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/mock"
)
type mockSerializer struct {
}

func (*mockSerializer) WriteStringValue(key string, value *string) error {
return nil
}
func (*mockSerializer) WriteBoolValue(key string, value *bool) error {
return nil
}
func (*mockSerializer) WriteByteValue(key string, value *byte) error {
return nil
}
func (*mockSerializer) WriteInt8Value(key string, value *int8) error {
return nil
}
func (*mockSerializer) WriteInt32Value(key string, value *int32) error {
return nil
}
func (*mockSerializer) WriteInt64Value(key string, value *int64) error {
return nil
}
func (*mockSerializer) WriteFloat32Value(key string, value *float32) error {
return nil
}
func (*mockSerializer) WriteFloat64Value(key string, value *float64) error {
return nil
}
func (*mockSerializer) WriteByteArrayValue(key string, value []byte) error {
return nil
}
func (*mockSerializer) WriteTimeValue(key string, value *time.Time) error {
return nil
}
func (*mockSerializer) WriteISODurationValue(key string, value *serialization.ISODuration) error {
return nil
}
func (*mockSerializer) WriteDateOnlyValue(key string, value *serialization.DateOnly) error {
return nil
}
func (*mockSerializer) WriteTimeOnlyValue(key string, value *serialization.TimeOnly) error {
return nil
}
func (*mockSerializer) WriteUUIDValue(key string, value *uuid.UUID) error {
return nil
}
func (*mockSerializer) WriteObjectValue(key string, item serialization.Parsable) error {
return nil
}
func (*mockSerializer) WriteCollectionOfObjectValues(key string, collection []serialization.Parsable) error {
return nil
}
func (*mockSerializer) WriteCollectionOfStringValues(key string, collection []string) error {
return nil
}
func (*mockSerializer) WriteCollectionOfBoolValues(key string, collection []bool) error {
return nil
}
func (*mockSerializer) WriteCollectionOfByteValues(key string, collection []byte) error {
return nil
}
func (*mockSerializer) WriteCollectionOfInt8Values(key string, collection []int8) error {
return nil
}
func (*mockSerializer) WriteCollectionOfInt32Values(key string, collection []int32) error {
return nil
}
func (*mockSerializer) WriteCollectionOfInt64Values(key string, collection []int64) error {
return nil
}
func (*mockSerializer) WriteCollectionOfFloat32Values(key string, collection []float32) error {
return nil
}
func (*mockSerializer) WriteCollectionOfFloat64Values(key string, collection []float64) error {
return nil
}
func (*mockSerializer) WriteCollectionOfTimeValues(key string, collection []time.Time) error {
return nil
}
func (*mockSerializer) WriteCollectionOfISODurationValues(key string, collection []serialization.ISODuration) error {
return nil
}
func (*mockSerializer) WriteCollectionOfDateOnlyValues(key string, collection []serialization.DateOnly) error {
return nil
}
func (*mockSerializer) WriteCollectionOfTimeOnlyValues(key string, collection []serialization.TimeOnly) error {
return nil
}
func (*mockSerializer) WriteCollectionOfUUIDValues(key string, collection []uuid.UUID) error {
return nil
}
func (*mockSerializer) GetSerializedContent() ([]byte, error) {
return nil, nil
}
func (*mockSerializer) WriteAdditionalData(value map[string]interface{}) error {
return nil
}
func (*mockSerializer) Close() error {
return nil
}

type mockSerializerFactory struct {
}

func (*mockSerializerFactory) GetValidContentType() (string, error) {
return "application/json", nil
}
func (*mockSerializerFactory) GetSerializationWriter(contentType string) (serialization.SerializationWriter, error) {
return &mockSerializer{}, nil
}

func TestItGetsVendorSpecificSerializationWriter(t *testing.T) {
registry := NewSerializationWriterFactoryRegistry()
registry.ContentTypeAssociatedFactories["application/json"] = &mockSerializerFactory{}
serializationWriter = registry.GetSerializationWriter("application/vnd+json")
assert.NotNil(t, serializationWriter)
}
3 changes: 2 additions & 1 deletion abstractions/java/lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ repositories {
dependencies {
// Use JUnit Jupiter API for testing.
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testImplementation 'org.mockito:mockito-inline:4.4.0'

// Use JUnit Jupiter Engine for testing.
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
Expand All @@ -48,7 +49,7 @@ publishing {
publications {
gpr(MavenPublication) {
artifactId 'kiota-abstractions'
version '1.0.29'
version '1.0.30'
from(components.java)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@
import java.io.InputStream;
import java.util.HashMap;
import java.util.Objects;
import java.util.regex.Pattern;

import javax.annotation.Nonnull;

/**
* This factory holds a list of all the registered factories for the various types of nodes.
*/
public class ParseNodeFactoryRegistry implements ParseNodeFactory {
/** Default singleton instance of the registry to be used when registring new factories that should be available by default. */
/** Default singleton instance of the registry to be used when registering new factories that should be available by default. */
public static final ParseNodeFactoryRegistry defaultInstance = new ParseNodeFactoryRegistry();
/** List of factories that are registered by content type. */
public HashMap<String, ParseNodeFactory> contentTypeAssociatedFactories = new HashMap<>();
public String getValidContentType() {
throw new UnsupportedOperationException("The registry supports multiple content types. Get the registered factory instead.");
}
private static Pattern contentTypeVendorCleanupPattern = Pattern.compile("[^/]+\\+", Pattern.CASE_INSENSITIVE);
@Override
@Nonnull
public ParseNode getParseNode(@Nonnull final String contentType, @Nonnull final InputStream rawResponse) {
Expand All @@ -25,10 +27,14 @@ public ParseNode getParseNode(@Nonnull final String contentType, @Nonnull final
if(contentType.isEmpty()) {
throw new NullPointerException("contentType cannot be empty");
}
if(contentTypeAssociatedFactories.containsKey(contentType)) {
return contentTypeAssociatedFactories.get(contentType).getParseNode(contentType, rawResponse);
} else {
throw new RuntimeException("Content type " + contentType + " does not have a factory to be parsed");
final String vendorSpecificContentType = contentType.split(";")[0];
if(contentTypeAssociatedFactories.containsKey(vendorSpecificContentType)) {
return contentTypeAssociatedFactories.get(vendorSpecificContentType).getParseNode(vendorSpecificContentType, rawResponse);
}
final String cleanedContentType = contentTypeVendorCleanupPattern.matcher(vendorSpecificContentType).replaceAll("");
if(contentTypeAssociatedFactories.containsKey(cleanedContentType)) {
return contentTypeAssociatedFactories.get(cleanedContentType).getParseNode(cleanedContentType, rawResponse);
}
throw new RuntimeException("Content type " + cleanedContentType + " does not have a factory to be parsed");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,35 @@

import java.util.HashMap;
import java.util.Objects;
import java.util.regex.Pattern;

import javax.annotation.Nonnull;
/** This factory holds a list of all the registered factories for the various types of nodes. */
public class SerializationWriterFactoryRegistry implements SerializationWriterFactory {
/** Default singleton instance of the registry to be used when registring new factories that should be available by default. */
/** Default singleton instance of the registry to be used when registering new factories that should be available by default. */
public final static SerializationWriterFactoryRegistry defaultInstance = new SerializationWriterFactoryRegistry();
/** List of factories that are registered by content type. */
public HashMap<String, SerializationWriterFactory> contentTypeAssociatedFactories = new HashMap<>();
public String getValidContentType() {
throw new UnsupportedOperationException("The registry supports multiple content types. Get the registered factory instead.");
}
private static Pattern contentTypeVendorCleanupPattern = Pattern.compile("[^/]+\\+", Pattern.CASE_INSENSITIVE);
@Override
@Nonnull
public SerializationWriter getSerializationWriter(@Nonnull final String contentType) {
Objects.requireNonNull(contentType, "parameter contentType cannot be null");
if(contentType.isEmpty()) {
throw new NullPointerException("contentType cannot be empty");
}
if(contentTypeAssociatedFactories.containsKey(contentType)) {
return contentTypeAssociatedFactories.get(contentType).getSerializationWriter(contentType);
} else {
throw new RuntimeException("Content type " + contentType + " does not have a factory to be serialized");
final String vendorSpecificContentType = contentType.split(";")[0];
if(contentTypeAssociatedFactories.containsKey(vendorSpecificContentType)) {
return contentTypeAssociatedFactories.get(vendorSpecificContentType).getSerializationWriter(vendorSpecificContentType);
}
final String cleanedContentType = contentTypeVendorCleanupPattern.matcher(vendorSpecificContentType).replaceAll("");
if(contentTypeAssociatedFactories.containsKey(cleanedContentType)) {
return contentTypeAssociatedFactories.get(cleanedContentType).getSerializationWriter(cleanedContentType);
}
throw new RuntimeException("Content type " + contentType + " does not have a factory to be serialized");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.microsoft.kiota;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

import com.microsoft.kiota.serialization.ParseNodeFactoryRegistry;
import com.microsoft.kiota.serialization.ParseNode;
import com.microsoft.kiota.serialization.ParseNodeFactory;

class ParseNodeFactoryRegistryTest {
@Test
void getsVendorSpecificParseNodeFactory() throws IOException {
final var registry = new ParseNodeFactoryRegistry();
final var parseNodeFactoryMock = mock(ParseNodeFactory.class);
final var parseNodeMock = mock(ParseNode.class);
when(parseNodeFactoryMock.getValidContentType()).thenReturn("application/json");
when(parseNodeFactoryMock.getParseNode(anyString(), any(InputStream.class))).thenReturn(parseNodeMock);
registry.contentTypeAssociatedFactories.put("application/json", parseNodeFactoryMock);
final var str = "{\"test\":\"test\"}";
try (final var payloadMock = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8))) {
final var parseNode = registry.getParseNode("application/vnd+json", payloadMock);
assertNotNull(parseNode);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.microsoft.kiota;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.IOException;

import com.microsoft.kiota.serialization.SerializationWriterFactoryRegistry;
import com.microsoft.kiota.serialization.SerializationWriter;
import com.microsoft.kiota.serialization.SerializationWriterFactory;

class SerializationWriterFactoryRegistryTest {
@Test
void getsVendorSpecificSerializationWriterFactory() throws IOException {
final var registry = new SerializationWriterFactoryRegistry();
final var serializationWriterFactoryMock = mock(SerializationWriterFactory.class);
final var serializationWriterMock = mock(SerializationWriter.class);
when(serializationWriterFactoryMock.getValidContentType()).thenReturn("application/json");
when(serializationWriterFactoryMock.getSerializationWriter(anyString())).thenReturn(serializationWriterMock);
registry.contentTypeAssociatedFactories.put("application/json", serializationWriterFactoryMock);
final var parseNode = registry.getSerializationWriter("application/vnd+json");
assertNotNull(parseNode);
}
}
Loading

0 comments on commit 438918a

Please sign in to comment.