feat: add SchemaOrgRecipe types and FromJSON parsing

This commit is contained in:
2026-05-19 22:44:03 -04:00
parent 09952f0c43
commit 9586afcb46
3 changed files with 278 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
module Roux.SchemaOrgSpec (spec) where
import qualified Data.Aeson as A
import Data.Maybe (fromJust, isJust)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Test.Hspec (Spec, describe, expectationFailure, it, shouldBe, shouldSatisfy)
import Roux.SchemaOrg
-- | Extract the first schema.org Recipe JSON-LD from HTML.
tryDecode :: Text -> Maybe A.Value
tryDecode html =
case T.splitOn "<script type=\"application/ld+json" html of
(_before : rest : _) ->
-- Skip past the closing > of the opening script tag, which may have extra attributes
let afterOpenTag = T.drop 1 (T.dropWhile (/= '>') rest)
in case T.splitOn "</script>" afterOpenTag of
(jsonContent : _) -> A.decodeStrict (TE.encodeUtf8 (T.strip jsonContent))
_ -> Nothing
_ -> Nothing
spec :: Spec
spec = describe "SchemaOrg" $ do
describe "parseSchemaOrgRecipe" $ do
it "parses fried-rice.html embedded JSON-LD" $ do
html <- readFile "test-data/fried-rice.html"
let decoded = tryDecode (T.pack html)
decoded `shouldSatisfy` isJust
let recipe = parseSchemaOrgRecipe (fromJust decoded)
recipe `shouldSatisfy` isJust
case recipe of
Nothing -> expectationFailure "expected Just SchemaOrgRecipe, got Nothing"
Just r -> do
soName r `shouldBe` "Fried Rice"
soRecipeYield r `shouldBe` Just "4 to 6 servings"
soTotalTime r `shouldBe` Just "PT20M"
soRecipeCuisine r `shouldBe` Just "asian"
length (soRecipeIngredient r) `shouldBe` 14
length (soRecipeInstructions r) `shouldBe` 4
it "parses carrot-risotto.html embedded JSON-LD" $ do
html <- readFile "test-data/carrot-risotto.html"
let decoded = tryDecode (T.pack html)
decoded `shouldSatisfy` isJust
let recipe = parseSchemaOrgRecipe (fromJust decoded)
recipe `shouldSatisfy` isJust
case recipe of
Nothing -> expectationFailure "expected Just SchemaOrgRecipe, got Nothing"
Just r -> soName r `shouldBe` "Carrot Risotto With Chile Crisp"
+3
View File
@@ -5,14 +5,17 @@ import Test.Tasty.Hspec (testSpec)
import qualified Roux.NYTimesSpec
import qualified Roux.ParserSpec
import qualified Roux.SchemaOrgSpec
main :: IO ()
main = do
parserTests <- testSpec "Parser" Roux.ParserSpec.spec
nytTests <- testSpec "NYTimes" Roux.NYTimesSpec.spec
schemaOrgTests <- testSpec "SchemaOrg" Roux.SchemaOrgSpec.spec
defaultMain $
testGroup
"roux-server"
[ parserTests
, nytTests
, schemaOrgTests
]