You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: yaml.go
    path of file: ./repos/porter/internal/porter_app/v1
    
    the code of the file until where you have to start completion: package v1

import (
	"context"
	"errors"
	"fmt"
	"math"
	"sort"
	"strconv"
	"strings"

	porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
	"github.com/porter-dev/porter/internal/telemetry"

	"gopkg.in/yaml.v2"
)

// AppProtoFromYaml converts an old version Porter YAML file into a PorterApp proto object
func AppProtoFromYaml(ctx context.Context, porterYamlBytes []byte) (*porterv1.PorterApp, map[string]string, error) {
	ctx, span := telemetry.NewSpan(ctx, "v1-app-proto-from-yaml")
	defer span.End()

	if porterYamlBytes == nil {
		return nil, nil, telemetry.Error(ctx, span, nil, "porter yaml is nil")
	}

	porterYaml := &PorterYAML{}
	err := yaml.Unmarshal(porterYamlBytes, porterYaml)
	if err != nil {
		return nil, nil, telemetry.Error(ctx, span, err, "error unmarshaling porter yaml")
	}

	appProto := &porterv1.PorterApp{}

	if porterYaml.Build != nil {
		appProto.Build = &porterv1.Build{
			Context:    porterYaml.Build.Context,
			Method:     porterYaml.Build.Method,
			Builder:    porterYaml.Build.Builder,
			Buildpacks: porterYaml.Build.Buildpacks,
			Dockerfile: porterYaml.Build.Dockerfile,
		}
	}

	if porterYaml.Build != nil && porterYaml.Build.Image != "" {
		imageSpl := strings.Split(porterYaml.Build.Image, ":")
		if len(imageSpl) == 2 {
			appProto.Image = &porterv1.AppImage{
				Repository: imageSpl[0],
				Tag:        imageSpl[1],
			}
		} else {
			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "image", Value: porterYaml.Build.Image})
			return nil, nil, telemetry.Error(ctx, span, err, "error parsing image")
		}
	}

	if porterYaml.Apps != nil && porterYaml.Services != nil {
		return nil, nil, telemetry.Error(ctx, span, nil, "'apps' and 'services' are synonymous but both were defined")
	}
	var services map[string]Service
	if porterYaml.Apps != nil {
		services = porterYaml.Apps
	}

	if porterYaml.Services != nil {
		services = porterYaml.Services
	}

	if services == nil {
		return nil, nil, telemetry.Error(ctx, span, nil, "porter yaml is missing services")
	}

	var serviceList []*porterv1.Service

	for name, service := range services {
		serviceType := protoEnumFromType(name, service)

		serviceProto, err := serviceProtoFromConfig(service, serviceType)
		if err != nil {
			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "failing-service-name", Value: name})
			return nil, nil, telemetry.Error(ctx, span, err, "error casting service config")
		}
		serviceProto.Name = name

    
func serviceProtoFromConfig(service Service, serviceType porterv1.ServiceType) (*porterv1.Service, error) {
	serviceProto := &porterv1.Service{
		RunOptional: service.Run,
		Type:        serviceType,
	}

	if service.Config.ReplicaCount != nil {
		// if the revision number cannot be converted, it will default to 0
		replicaCount, _ := strconv.Atoi(*service.Config.ReplicaCount)
		if replicaCount < math.MinInt32 || replicaCount > math.MaxInt32 {
			return nil, fmt.Errorf("replica count is out of range of int32")
		}
		// nolint:gosec
		int32Value := int32(replicaCount)
		serviceProto.InstancesOptional = &int32Value
	}

	if service.Config.Resources.Requests.Cpu != "" {
		cpuCoresStr := service.Config.Resources.Requests.Cpu
		if !strings.HasSuffix(cpuCoresStr, "m") {
			return nil, fmt.Errorf("cpu is not in millicores")
		}

		cpuCoresStr = strings.TrimSuffix(cpuCoresStr, "m")
		cpuCoresFloat64, err := strconv.ParseFloat(cpuCoresStr, 32)
		if err != nil {
			return nil, fmt.Errorf("cpu is not a float")
		}
		serviceProto.CpuCores = float32(cpuCoresFloat64) / 1000
	}

	if service.Config.Resources.Requests.Memory != "" {
		memoryStr := service.Config.Resources.Requests.Memory
		if !strings.HasSuffix(memoryStr, "Mi") {
			return nil, fmt.Errorf("memory is not in Mi")
		}

		memoryStr = strings.TrimSuffix(memoryStr, "Mi")
		memoryFloat64, err := strconv.ParseFloat(memoryStr, 32)
		if err != nil {
			return nil, fmt.Errorf("memory is not a float")
		}
		// nolint:gosec
		serviceProto.RamMegabytes = int32(memoryFloat64)
	}

	if service.Config.Container.Port != "" && service.Config.Service.Port != "" && service.Config.Container.Port != service.Config.Service.Port {
		return nil, errors.New("container port and service port do not match")
	}
	if service.Config.Container.Port != "" {
		port, err := strconv.Atoi(service.Config.Container.Port)
		if err != nil {
			return nil, fmt.Errorf("container port cannot be converted to int: %w", err)
		}
		if port < math.MinInt32 || port > math.MaxInt32 {
			return nil, fmt.Errorf("port is out of range of int32")
		}
		// nolint:gosec
		serviceProto.Port = int32(port)
	}
	if service.Config.Service.Port != "" {
		port, err := strconv.Atoi(service.Config.Service.Port)
		if err != nil {
			return nil, fmt.Errorf("service port cannot be converted to int: %w", err)
		}
		if port < math.MinInt32 || port > math.MaxInt32 {
			return nil, fmt.Errorf("port is out of range of int32")
		}
		// nolint:gosec
		serviceProto.Port = int32(port)
	}

	switch serviceType {
	default:
		return nil, fmt.Errorf("invalid service type '%s'", serviceType)
	case porterv1.ServiceType_SERVICE_TYPE_UNSPECIFIED:
		return nil, errors.New("service type unspecified")
	case porterv1.ServiceType_SERVICE_TYPE_WEB:
		webConfig, err := webConfigProtoFromConfig(service)
		if err != nil {
			return nil, fmt.Errorf("error converting web config: %w", err)
		}

		serviceProto.Config = &porterv1.Service_WebConfig{
			WebConfig: webConfig,
		}
	case porterv1.ServiceType_SERVICE_TYPE_WORKER:
		workerConfig, err := workerConfigProtoFromConfig(service)
		if err != nil {
			return nil, fmt.Errorf("error converting worker config: %w", err)
		}

		serviceProto.Config = &porterv1.Service_WorkerConfig{
			WorkerConfig: workerConfig,
		}
	case porterv1.ServiceType_SERVICE_TYPE_JOB:
		jobConfig := &porterv1.JobServiceConfig{
			AllowConcurrentOptional: service.Config.AllowConcurrency,
			Cron:                    service.Config.Schedule.Value,
		}

		serviceProto.Config = &porterv1.Service_JobConfig{
			JobConfig: jobConfig,
		}
	}

	return serviceProto, nil
}