Skip to content

Commit 0a9020a

Browse files
author
Reto Kühni
committed
1 parent d1fbfee commit 0a9020a

File tree

6 files changed

+105
-32
lines changed

6 files changed

+105
-32
lines changed

adapter/protobuf/mu-protobuf.cabal

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ library
4444
, sop-core >=0.5 && <0.6
4545
, template-haskell >=2.14 && <2.19
4646
, text >=1.2 && <2
47+
, filepath >=1.4 && <2
4748

4849
hs-source-dirs: src
4950
default-language: Haskell2010

adapter/protobuf/src/Mu/Quasi/GRpc.hs

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ and a set of 'Service's.
1010
-}
1111
module Mu.Quasi.GRpc (
1212
grpc
13-
) where
13+
) where
1414

1515
import Control.Monad.IO.Class
1616
import qualified Data.Text as T
@@ -35,12 +35,12 @@ grpc schemaName servicePrefix fp
3535
Left e
3636
-> fail ("could not parse protocol buffers spec: " ++ show e)
3737
Right p
38-
-> grpcToDecls schemaName servicePrefix p
38+
-> grpcToDecls schemaName servicePrefix p =<< loadImports fp p
3939

40-
grpcToDecls :: String -> (String -> String) -> P.ProtoBuf -> Q [Dec]
41-
grpcToDecls schemaName servicePrefix p@P.ProtoBuf { P.package = pkg, P.services = srvs }
40+
grpcToDecls :: String -> (String -> String) -> P.ProtoBuf -> [P.ProtoBuf] -> Q [Dec]
41+
grpcToDecls schemaName servicePrefix p@P.ProtoBuf { P.package = pkg, P.services = srvs } imps
4242
= do let schemaName' = mkName schemaName
43-
schemaDec <- protobufToDecls schemaName p
43+
schemaDec <- protobufToDecls schemaName p imps
4444
serviceTy <- mapM (pbServiceDeclToDec servicePrefix pkg schemaName') srvs
4545
pure (schemaDec ++ serviceTy)
4646

adapter/protobuf/src/Mu/Quasi/ProtoBuf.hs

+52-23
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
{-# language NamedFieldPuns #-}
55
{-# language OverloadedStrings #-}
66
{-# language TemplateHaskell #-}
7+
{-# language ViewPatterns #-}
78
{-|
89
Description : Quasi-quoters for Protocol Buffers schemas
910
@@ -16,19 +17,22 @@ module Mu.Quasi.ProtoBuf (
1617
protobuf
1718
-- * Only for internal use
1819
, protobufToDecls
20+
, loadImports
1921
) where
2022

21-
import Control.Monad (when)
23+
import Control.Monad (foldM, when)
2224
import Control.Monad.IO.Class
2325
import qualified Data.ByteString as B
2426
import Data.Int
2527
import qualified Data.List as L
26-
import Data.List.NonEmpty (NonEmpty (..))
28+
import Data.List.NonEmpty (NonEmpty (..), toList)
29+
import qualified Data.Map.Strict as M
2730
import qualified Data.Text as T
2831
import Data.Word
2932
import Language.Haskell.TH
3033
import Language.ProtocolBuffers.Parser
3134
import qualified Language.ProtocolBuffers.Types as P
35+
import System.FilePath (takeDirectory, (</>))
3236

3337
import Mu.Adapter.ProtoBuf
3438
import Mu.Schema.Annotations
@@ -44,13 +48,31 @@ protobuf schemaName fp
4448
Left e
4549
-> fail ("could not parse protocol buffers spec: " ++ show e)
4650
Right p
47-
-> protobufToDecls schemaName p
51+
-> protobufToDecls schemaName p =<< loadImports fp p
52+
53+
loadImports :: FilePath -> P.ProtoBuf -> Q [P.ProtoBuf]
54+
loadImports rootFp p = M.elems <$> loadImports' M.empty rootFp p
55+
where
56+
loadImports' :: M.Map FilePath P.ProtoBuf -> FilePath -> P.ProtoBuf -> Q (M.Map FilePath P.ProtoBuf)
57+
loadImports' m fp p' = foldM (loadImport fp) m $ P.imports p'
58+
loadImport :: FilePath -> M.Map FilePath P.ProtoBuf -> (P.ImportType, T.Text) -> Q (M.Map FilePath P.ProtoBuf)
59+
loadImport parentFp m (_, relFP) = do
60+
let fp = takeDirectory parentFp </> T.unpack relFP
61+
if fp `M.member` m
62+
then pure m
63+
else do
64+
r <- liftIO $ parseProtoBufFile fp
65+
case r of
66+
Left e -> do
67+
reportError $ "Include " <> fp <> " of " <> parentFp <> " not found: " <> show e
68+
pure m
69+
Right proto -> loadImports' (M.insert fp proto m) fp proto
4870

4971
-- | Shared portion of Protocol Buffers and gRPC quasi-quoters.
50-
protobufToDecls :: String -> P.ProtoBuf -> Q [Dec]
51-
protobufToDecls schemaName p
72+
protobufToDecls :: String -> P.ProtoBuf -> [P.ProtoBuf] -> Q [Dec]
73+
protobufToDecls schemaName p imps
5274
= do let schemaName' = mkName schemaName
53-
(schTy, annTy) <- schemaFromProtoBuf p
75+
(schTy, annTy) <- schemaFromProtoBuf p imps
5476
schemaDec <- tySynD schemaName' [] (pure schTy)
5577
#if MIN_VERSION_template_haskell(2,15,0)
5678
annDec <- tySynInstD (tySynEqn Nothing
@@ -62,29 +84,38 @@ protobufToDecls schemaName p
6284
#endif
6385
pure [schemaDec, annDec]
6486

65-
schemaFromProtoBuf :: P.ProtoBuf -> Q (Type, Type)
66-
schemaFromProtoBuf P.ProtoBuf {P.types = tys} = do
67-
let decls = flattenDecls (("", tys) :| []) tys
87+
schemaFromProtoBuf :: P.ProtoBuf -> [P.ProtoBuf] -> Q (Type, Type)
88+
schemaFromProtoBuf P.ProtoBuf {P.types = tys} imps = do
89+
let decls = flattenDecls (("", tys) :| []) tys <> flattenImportDecls imps
6890
(schTys, anns) <- unzip <$> mapM (pbTypeDeclToType $ shouldOptional decls) decls
6991
pure (typesToList schTys, typesToList (concat anns))
7092
where
7193
shouldOptional :: [P.TypeDeclaration] -> P.TypeName -> Bool
72-
shouldOptional [] _ = error "this should never happen"
94+
shouldOptional [] this = error $ T.unpack $ "no declaration for type " <> T.intercalate "." this
7395
shouldOptional (P.DMessage nm _ _ _ _ : _) this
7496
| nm == last this = True
7597
shouldOptional (P.DEnum nm _ _ : _) this
7698
| nm == last this = False
7799
shouldOptional (_ : rest) this
78100
= shouldOptional rest this
79101

102+
flattenImportDecls :: [P.ProtoBuf] -> [P.TypeDeclaration]
103+
flattenImportDecls = concatMap flattenImportDecls'
104+
where
105+
flattenImportDecls' :: P.ProtoBuf -> [P.TypeDeclaration]
106+
flattenImportDecls' P.ProtoBuf { P.types = tys, P.package = getPackageName -> pkg } =
107+
flattenDecls ((pkg, tys) :| []) tys
108+
getPackageName :: Maybe P.FullIdentifier -> T.Text
109+
getPackageName = maybe "" (T.intercalate ".")
110+
80111
flattenDecls :: NonEmpty (P.Identifier, [P.TypeDeclaration]) -> [P.TypeDeclaration] -> [P.TypeDeclaration]
81112
flattenDecls (currentScope :| higherScopes) = concatMap flattenDecl
82113
where
83114
flattenDecl (P.DEnum name o f) = [P.DEnum (prependCurrentScope name) o f]
84115
flattenDecl (P.DMessage name o r fs decls) =
85116
let newScopeName = prependCurrentScope name
86117
newScopes = (newScopeName, decls) :| (currentScope : higherScopes)
87-
in P.DMessage newScopeName o r (scopeFieldType newScopes <$> fs) [] : flattenDecls newScopes decls
118+
in P.DMessage newScopeName o r (scopeFieldType (toList newScopes) <$> fs) [] : flattenDecls newScopes decls
88119

89120
scopeFieldType scopes (P.NormalField frep ftype fname fnum fopts) =
90121
P.NormalField frep (qualifyType scopes ftype) fname fnum fopts
@@ -95,17 +126,15 @@ flattenDecls (currentScope :| higherScopes) = concatMap flattenDecl
95126
qualifyType scopes (P.TOther ts) = P.TOther (qualifyTOther scopes ts)
96127
qualifyType _scopes t = t
97128

98-
qualifyTOther _scopes [] = error "This shouldn't be possible"
99-
qualifyTOther ((_, _) :| []) ts =
100-
[T.intercalate "." ts] -- Top level scope, no need to search anything, use
101-
-- the name as is. Maybe we should search and fail
102-
-- if a type is not found even from top level, but
103-
-- that could be a lot of work as this function is
104-
-- pure right now.
105-
qualifyTOther ((scopeName, decls) :| (restFirst : restTail)) ts =
129+
qualifyTOther _ [] = error "This shouldn't be possible"
130+
qualifyTOther [] ts = [T.intercalate "." ts]
131+
qualifyTOther ((scopeName, decls) : rest) ts =
106132
if L.any (hasDeclFor ts) decls
107-
then [T.intercalate "." (scopeName:ts)]
108-
else qualifyTOther (restFirst :| restTail) ts
133+
then [qualifyName scopeName ts]
134+
else qualifyTOther rest ts
135+
where
136+
qualifyName "" ts' = T.intercalate "." ts'
137+
qualifyName sn ts'=qualifyName "" (sn:ts')
109138

110139
hasDeclFor [] _ = True
111140
hasDeclFor [t] (P.DEnum enumName _ _) = t == enumName
@@ -120,7 +149,7 @@ flattenDecls (currentScope :| higherScopes) = concatMap flattenDecl
120149
prependCurrentScope x =
121150
case fst currentScope of
122151
"" -> x
123-
_ -> fst currentScope <> "." <> x
152+
sn -> sn <> "." <> x
124153

125154
pbTypeDeclToType :: (P.TypeName -> Bool) -> P.TypeDeclaration -> Q (Type, [Type])
126155
pbTypeDeclToType _ (P.DEnum name _ fields) = do
@@ -179,7 +208,7 @@ pbTypeDeclToType shouldOptional (P.DMessage name _ _ fields _) = do
179208
pbFieldTypeToType P.TBool = [t|'TPrimitive Bool|]
180209
pbFieldTypeToType P.TString = [t|'TPrimitive T.Text|]
181210
pbFieldTypeToType P.TBytes = [t|'TPrimitive B.ByteString|]
182-
pbFieldTypeToType (P.TOther t) = [t|'TSchematic $(textToStrLit (last t))|]
211+
pbFieldTypeToType (P.TOther t) = [t|'TSchematic $(textToStrLit (T.intercalate "." t))|]
183212

184213
hasFieldNumber P.NormalField {} = True
185214
hasFieldNumber P.MapField {} = True

adapter/protobuf/src/Mu/Quasi/ProtoBuf/Example.hs

+6-4
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ module Mu.Quasi.ProtoBuf.Example where
1212
import Mu.Quasi.ProtoBuf
1313

1414
#if __GHCIDE__
15-
protobuf "ExampleProtoBufSchema" "adapter/protobuf/test/protobuf/example.proto"
16-
protobuf "Example2ProtoBufSchema" "adapter/protobuf/test/protobuf/example2.proto"
15+
protobuf "ExampleProtoBufSchema" "adapter/protobuf/test/protobuf/example.proto"
16+
protobuf "Example2ProtoBufSchema" "adapter/protobuf/test/protobuf/example2.proto"
17+
protobuf "ExampleWithImportsProtoBufSchema" "adapter/protobuf/test/protobuf/exampleWithImports.proto"
1718
#else
18-
protobuf "ExampleProtoBufSchema" "test/protobuf/example.proto"
19-
protobuf "Example2ProtoBufSchema" "test/protobuf/example2.proto"
19+
protobuf "ExampleProtoBufSchema" "test/protobuf/example.proto"
20+
protobuf "Example2ProtoBufSchema" "test/protobuf/example2.proto"
21+
protobuf "ExampleWithImportsProtoBufSchema" "test/protobuf/exampleWithImports.proto"
2022
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
syntax = "proto3";
2+
3+
import "import.proto";
4+
5+
message messageWithImports {
6+
imports.Message message = 1;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
syntax = "proto3";
2+
3+
package imports;
4+
5+
message Message {
6+
MessageA a = 1;
7+
oneof union {
8+
MessageB b = 2;
9+
MessageC c = 3;
10+
}
11+
Enum e = 4;
12+
Nested nested = 5;
13+
14+
message Nested {
15+
string value = 1;
16+
}
17+
}
18+
19+
message MessageA {
20+
string text = 1;
21+
}
22+
23+
message MessageB {
24+
string text = 1;
25+
}
26+
27+
message MessageC {
28+
string text = 1;
29+
}
30+
31+
enum Enum {
32+
Value1 = 0;
33+
Value2 = 1;
34+
}

0 commit comments

Comments
 (0)