implement grpc gateway for http api
This commit is contained in:
		
							
								
								
									
										66
									
								
								pkg/ambient/ambientgrpc/grpc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								pkg/ambient/ambientgrpc/grpc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| package ambientgrpc | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/attribute" | ||||
| 	otelcodes "go.opentelemetry.io/otel/codes" | ||||
| 	"go.opentelemetry.io/otel/trace" | ||||
| 	"google.golang.org/grpc/codes" | ||||
| 	"google.golang.org/grpc/status" | ||||
| 	"google.golang.org/protobuf/types/known/timestamppb" | ||||
|  | ||||
| 	"gitea.libretechconsulting.com/rmcguire/go-app/pkg/otel" | ||||
|  | ||||
| 	pb "gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/api/v1alpha1/weather" | ||||
| 	"gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/pkg/weather/recorder" | ||||
| ) | ||||
|  | ||||
| type GRPCWeather struct { | ||||
| 	ctx      context.Context | ||||
| 	recorder *recorder.WeatherRecorder | ||||
| 	tracer   trace.Tracer | ||||
| 	*pb.UnimplementedAmbientLocalWeatherServiceServer | ||||
| } | ||||
|  | ||||
| func NewGRPCWeather(ctx context.Context, recorder *recorder.WeatherRecorder) *GRPCWeather { | ||||
| 	return &GRPCWeather{ | ||||
| 		ctx:      ctx, | ||||
| 		recorder: recorder, | ||||
| 		tracer:   otel.GetTracer(ctx, "grpcWeather"), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (w *GRPCWeather) GetWeather(ctx context.Context, req *pb.GetWeatherRequest) ( | ||||
| 	*pb.GetWeatherResponse, error, | ||||
| ) { | ||||
| 	ctx, span := w.tracer.Start(ctx, "getWeather") | ||||
| 	defer span.End() | ||||
|  | ||||
| 	limit := 1 | ||||
| 	if req.Limit != nil { | ||||
| 		if *req.Limit > 1 { | ||||
| 			limit = int(*req.Limit) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	span.SetAttributes(attribute.Int("limit", limit)) | ||||
|  | ||||
| 	updates, err := w.recorder.Get(ctx, req) | ||||
| 	if err != nil { | ||||
| 		span.RecordError(err) | ||||
| 		span.SetStatus(otelcodes.Error, err.Error()) | ||||
| 		return nil, status.Error(codes.Internal, err.Error()) | ||||
| 	} else if len(updates) < 1 { | ||||
| 		return nil, status.Error(codes.OutOfRange, "no weather available") | ||||
| 	} | ||||
|  | ||||
| 	span.SetStatus(otelcodes.Ok, "") | ||||
|  | ||||
| 	return &pb.GetWeatherResponse{ | ||||
| 		LastUpdated: ×tamppb.Timestamp{ | ||||
| 			Seconds: updates[0].DateUTC.Unix(), | ||||
| 		}, | ||||
| 		WeatherUpdates: UpdatesToPbUpdates(updates), | ||||
| 	}, nil | ||||
| } | ||||
							
								
								
									
										90
									
								
								pkg/ambient/ambientgrpc/mapupdate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								pkg/ambient/ambientgrpc/mapupdate.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| package ambientgrpc | ||||
|  | ||||
| import ( | ||||
| 	"slices" | ||||
| 	"time" | ||||
|  | ||||
| 	"google.golang.org/protobuf/types/known/timestamppb" | ||||
|  | ||||
| 	pb "gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/api/v1alpha1/weather" | ||||
| 	"gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/pkg/util" | ||||
| 	"gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/pkg/weather" | ||||
| ) | ||||
|  | ||||
| // Converts a slice of weather.WeatherUpdate to proto structs | ||||
| func UpdatesToPbUpdates(u []*weather.WeatherUpdate) []*pb.WeatherUpdate { | ||||
| 	updates := make([]*pb.WeatherUpdate, len(u)) | ||||
| 	for i, update := range u { | ||||
| 		updates[i] = UpdateToPbUpdate(update) | ||||
| 	} | ||||
| 	return updates | ||||
| } | ||||
|  | ||||
| // Maps a weather.WeatherUpdate to the proto version. | ||||
| // | ||||
| // This should be auto-generated code, but as the proto should be | ||||
| // stable, it is probably not worth the effort. | ||||
| func UpdateToPbUpdate(u *weather.WeatherUpdate) *pb.WeatherUpdate { | ||||
| 	if u.DateUTC == nil { | ||||
| 		now := time.Now() | ||||
| 		u.DateUTC = &now | ||||
| 	} | ||||
|  | ||||
| 	return &pb.WeatherUpdate{ | ||||
| 		StationName:         u.StationConfig.Name, | ||||
| 		StationType:         util.DerefStr(u.StationType), | ||||
| 		StationId:           util.DerefStr(u.StationID), | ||||
| 		TempOutdoorF:        u.TempOutdoorF, | ||||
| 		TempIndoorF:         u.TempIndoorF, | ||||
| 		HumidityOutdoor:     util.Int32ptr(u.HumidityOudoor), | ||||
| 		HumidityIndoor:      util.Int32ptr(u.HumidityIndoor), | ||||
| 		WindSpeedMph:        u.WindSpeedMPH, | ||||
| 		WindGustMph:         u.WindGustMPH, | ||||
| 		MaxDailyGust:        u.MaxDailyGust, | ||||
| 		WindDir:             util.Int32ptr(u.WindDir), | ||||
| 		WindDirAvg_10M:      util.Int32ptr(u.WindDirAvg10m), | ||||
| 		Uv:                  util.Int32ptr(u.UV), | ||||
| 		SolarRadiation:      u.SolarRadiation, | ||||
| 		HourlyRainIn:        u.HourlyRainIn, | ||||
| 		EventRainIn:         u.EventRainIn, | ||||
| 		DailyRainIn:         u.DailyRainIn, | ||||
| 		WeeklyRainIn:        u.WeeklyRainIn, | ||||
| 		MonthlyRainIn:       u.MonthlyRainIn, | ||||
| 		YearlyRainIn:        u.YearlyRainIn, | ||||
| 		TotalRainIn:         u.TotalRainIn, | ||||
| 		Batteries:           batteriesToPbBatteries(u.Batteries), | ||||
| 		BaromRelativeIn:     u.BaromRelativeIn, | ||||
| 		BaromAbsoluteIn:     u.BaromAbsoluteIn, | ||||
| 		DewPointF:           u.DewPointF, | ||||
| 		WindChillF:          u.WindChillF, | ||||
| 		TempHumiditySensors: thSensorsToPbSensors(u.TempHumiditySensors), | ||||
| 		UpdateTimestamp:     timestamppb.New(*u.DateUTC), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func batteriesToPbBatteries(batteries []weather.BatteryStatus) []*pb.BatteryStatus { | ||||
| 	pbBatteries := make([]*pb.BatteryStatus, 0, len(batteries)) | ||||
| 	for _, b := range batteries { | ||||
| 		if b.Status != nil { | ||||
| 			pbBatteries = append(pbBatteries, &pb.BatteryStatus{ | ||||
| 				Component: b.Component, | ||||
| 				Status:    util.Int32ptr(b.Status), | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| 	return slices.Clip(pbBatteries) | ||||
| } | ||||
|  | ||||
| func thSensorsToPbSensors(sensors []*weather.TempHumiditySensor) []*pb.TempHumiditySensor { | ||||
| 	pbSensors := make([]*pb.TempHumiditySensor, 0, len(sensors)) | ||||
| 	for _, s := range sensors { | ||||
| 		if s.TempF != nil || s.Humidity != nil { | ||||
| 			pbSensors = append(pbSensors, &pb.TempHumiditySensor{ | ||||
| 				Name:     s.Name, | ||||
| 				TempF:    s.TempF, | ||||
| 				Humidity: util.Int32ptr(s.Humidity), | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| 	return slices.Clip(pbSensors) | ||||
| } | ||||
							
								
								
									
										117
									
								
								pkg/ambient/ambientgrpc/mapupdate_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								pkg/ambient/ambientgrpc/mapupdate_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | ||||
| package ambientgrpc | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"google.golang.org/protobuf/types/known/timestamppb" | ||||
| 	"k8s.io/utils/ptr" | ||||
|  | ||||
| 	pb "gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/api/v1alpha1/weather" | ||||
| 	"gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/pkg/ambient/config" | ||||
| 	"gitea.libretechconsulting.com/rmcguire/ambient-local-exporter/pkg/weather" | ||||
| ) | ||||
|  | ||||
| var now = time.Now() | ||||
|  | ||||
| var mockUpdate = &weather.WeatherUpdate{ | ||||
| 	DateUTC: &now, | ||||
| 	StationConfig: &config.WeatherStation{ | ||||
| 		Name:      "50W", | ||||
| 		Equipment: "WS-5000", | ||||
| 	}, | ||||
| 	StationID:      ptr.To("50W"), | ||||
| 	StationType:    ptr.To("WS-5000"), | ||||
| 	TempOutdoorF:   ptr.To(97.6), | ||||
| 	TempIndoorF:    ptr.To(77.6), | ||||
| 	HumidityOudoor: ptr.To(50), | ||||
| 	HumidityIndoor: ptr.To(50), | ||||
| 	WindSpeedMPH:   ptr.To(20.5), | ||||
| 	WindGustMPH:    ptr.To(30.5), | ||||
| 	MaxDailyGust:   ptr.To(40.0), | ||||
| 	WindDir:        ptr.To(180), | ||||
| 	WindDirAvg10m:  ptr.To(180), | ||||
| 	UV:             nil, | ||||
| 	SolarRadiation: ptr.To(9.999), | ||||
| 	HourlyRainIn:   ptr.To(9.999), | ||||
| 	EventRainIn:    ptr.To(9.999), | ||||
| 	DailyRainIn:    ptr.To(9.999), | ||||
| 	WeeklyRainIn:   ptr.To(9.999), | ||||
| 	MonthlyRainIn:  ptr.To(9.999), | ||||
| 	YearlyRainIn:   ptr.To(9.999), | ||||
| 	TotalRainIn:    ptr.To(9.999), | ||||
| 	Batteries: []weather.BatteryStatus{ | ||||
| 		{Component: "battery1", Status: ptr.To(1)}, | ||||
| 		{Component: "battery2", Status: ptr.To(1)}, | ||||
| 	}, | ||||
| 	BaromRelativeIn: ptr.To(9.999), | ||||
| 	BaromAbsoluteIn: ptr.To(9.999), | ||||
| 	DewPointF:       ptr.To(9.999), | ||||
| 	WindChillF:      ptr.To(9.999), | ||||
| 	TempHumiditySensors: []*weather.TempHumiditySensor{ | ||||
| 		{Name: "sensor1", TempF: ptr.To(99.999), Humidity: nil}, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func TestUpdateToPbUpdate(t *testing.T) { | ||||
| 	type args struct { | ||||
| 		u *weather.WeatherUpdate | ||||
| 	} | ||||
| 	tests := []struct { | ||||
| 		name string | ||||
| 		args args | ||||
| 		want *pb.WeatherUpdate | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "Map Update to PB", | ||||
| 			args: args{u: mockUpdate}, | ||||
| 			want: &pb.WeatherUpdate{ | ||||
| 				StationName:     mockUpdate.StationConfig.Name, | ||||
| 				StationType:     *mockUpdate.StationType, | ||||
| 				StationId:       *mockUpdate.StationID, | ||||
| 				TempOutdoorF:    mockUpdate.TempOutdoorF, | ||||
| 				TempIndoorF:     mockUpdate.TempIndoorF, | ||||
| 				HumidityOutdoor: ptr.To(int32(*mockUpdate.HumidityOudoor)), | ||||
| 				HumidityIndoor:  ptr.To(int32(*mockUpdate.HumidityIndoor)), | ||||
| 				WindSpeedMph:    mockUpdate.WindSpeedMPH, | ||||
| 				WindGustMph:     mockUpdate.WindGustMPH, | ||||
| 				MaxDailyGust:    mockUpdate.MaxDailyGust, | ||||
| 				WindDir:         ptr.To(int32(*mockUpdate.WindDir)), | ||||
| 				WindDirAvg_10M:  ptr.To(int32(*mockUpdate.WindDirAvg10m)), | ||||
| 				Uv:              nil, | ||||
| 				SolarRadiation:  mockUpdate.SolarRadiation, | ||||
| 				HourlyRainIn:    mockUpdate.HourlyRainIn, | ||||
| 				EventRainIn:     mockUpdate.EventRainIn, | ||||
| 				DailyRainIn:     mockUpdate.DailyRainIn, | ||||
| 				WeeklyRainIn:    mockUpdate.WeeklyRainIn, | ||||
| 				MonthlyRainIn:   mockUpdate.MonthlyRainIn, | ||||
| 				YearlyRainIn:    mockUpdate.YearlyRainIn, | ||||
| 				TotalRainIn:     mockUpdate.TotalRainIn, | ||||
| 				Batteries: []*pb.BatteryStatus{ | ||||
| 					{Component: mockUpdate.Batteries[0].Component, Status: ptr.To(int32(*mockUpdate.Batteries[0].Status))}, | ||||
| 					{Component: mockUpdate.Batteries[1].Component, Status: ptr.To(int32(*mockUpdate.Batteries[1].Status))}, | ||||
| 				}, | ||||
| 				BaromRelativeIn: mockUpdate.BaromRelativeIn, | ||||
| 				BaromAbsoluteIn: mockUpdate.BaromAbsoluteIn, | ||||
| 				DewPointF:       mockUpdate.DewPointF, | ||||
| 				WindChillF:      mockUpdate.WindChillF, | ||||
| 				TempHumiditySensors: []*pb.TempHumiditySensor{ | ||||
| 					{ | ||||
| 						Name:     mockUpdate.TempHumiditySensors[0].Name, | ||||
| 						TempF:    mockUpdate.TempHumiditySensors[0].TempF, | ||||
| 						Humidity: nil, | ||||
| 					}, | ||||
| 				}, | ||||
| 				UpdateTimestamp: timestamppb.New(now), | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			if got := UpdateToPbUpdate(tt.args.u); !reflect.DeepEqual(got, tt.want) { | ||||
| 				t.Errorf("UpdateToPbUpdate() = %v, want %v", got, tt.want) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user